diff --git a/.all-contributorsrc b/.all-contributorsrc
deleted file mode 100644
index 158515252..000000000
--- a/.all-contributorsrc
+++ /dev/null
@@ -1,372 +0,0 @@
-{
- "projectName": "AFFiNE",
- "projectOwner": "toeverything",
- "repoType": "github",
- "repoHost": "https://github.com",
- "files": [
- "README.md"
- ],
- "imageSize": 50,
- "commit": false,
- "commitConvention": "angular",
- "contributorsPerLine": 7,
- "badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
- "contributors": [
- {
- "login": "doodlewind",
- "name": "Yifeng Wang",
- "avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4",
- "profile": "https://github.com/doodlewind",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "darkskygit",
- "name": "DarkSky",
- "avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4",
- "profile": "https://darksky.eu.org/",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "tzhangchi",
- "name": "Chi Zhang",
- "avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4",
- "profile": "http://zhangchi.page/",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "alt1o",
- "name": "wang xinglong",
- "avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4",
- "profile": "https://github.com/alt1o",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "DiamondThree",
- "name": "DiamondThree",
- "avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4",
- "profile": "https://github.com/DiamondThree",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "lawvs",
- "name": "Whitewater",
- "avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4",
- "profile": "https://lawvs.github.io/profile/",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "zuoxiaodong0815",
- "name": "xiaodong zuo",
- "avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4",
- "profile": "https://github.com/zuoxiaodong0815",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "SaikaSakura",
- "name": "MingLIang Wang",
- "avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4",
- "profile": "https://github.com/SaikaSakura",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "QiShaoXuan",
- "name": "Qi",
- "avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4",
- "profile": "https://github.com/QiShaoXuan",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "mitsuhatu",
- "name": "mitsuhatu",
- "avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4",
- "profile": "https://github.com/mitsuhatu",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "Austaras",
- "name": "Austaras",
- "avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4",
- "profile": "https://shockwave.me/",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "uptonking",
- "name": "Jin Yao",
- "avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
- "profile": "https://github.com/uptonking",
- "contributions": [
- "code",
- "doc"
- ]
- },
- {
- "login": "HeJiachen-PM",
- "name": "HeJiachen-PM",
- "avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
- "profile": "https://github.com/HeJiachen-PM",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "Yipei-Operation",
- "name": "Yipei Wei",
- "avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
- "profile": "https://github.com/Yipei-Operation",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "fanjing22",
- "name": "fanjing22",
- "avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
- "profile": "https://github.com/fanjing22",
- "contributions": [
- "design"
- ]
- },
- {
- "login": "Svaney-ssman",
- "name": "Svaney",
- "avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
- "profile": "https://github.com/Svaney-ssman",
- "contributions": [
- "design"
- ]
- },
- {
- "login": "xell",
- "name": "Guozhu Liu",
- "avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
- "profile": "http://xell.me/",
- "contributions": [
- "design"
- ]
- },
- {
- "login": "fyZheng07",
- "name": "fyZheng07",
- "avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
- "profile": "https://github.com/fyZheng07",
- "contributions": [
- "eventOrganizing",
- "userTesting"
- ]
- },
- {
- "login": "CJSS",
- "name": "CJSS",
- "avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
- "profile": "https://github.com/CJSS",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "JimmFly",
- "name": "JimmFly",
- "avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
- "profile": "https://github.com/JimmFly",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "CarlosZoft",
- "name": "Carlos Rafael ",
- "avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
- "profile": "https://github.com/CarlosZoft",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "caleboleary",
- "name": "Caleb OLeary",
- "avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
- "profile": "https://github.com/caleboleary",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "westongraham",
- "name": "Weston Graham",
- "avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
- "profile": "https://github.com/westongraham",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "pointmax",
- "name": "pointmax",
- "avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
- "profile": "https://github.com/pointmax",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "liby",
- "name": "Bryan Lee",
- "avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
- "profile": "https://liby.github.io/notes",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "chenmoonmo",
- "name": "Simon Li",
- "avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
- "profile": "https://github.com/chenmoonmo",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "githbq",
- "name": "Bob Hu",
- "avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
- "profile": "https://github.com/githbq",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "lucky-chap",
- "name": "Quavo",
- "avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
- "profile": "https://quavo.vercel.app/",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "LuciNyan",
- "name": "子瞻 Luci",
- "avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4",
- "profile": "https://github.com/LuciNyan",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "m1911star",
- "name": "Horus",
- "avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4",
- "profile": "http://blog.ipili.me/",
- "contributions": [
- "code",
- "platform"
- ]
- },
- {
- "login": "fanshyiis",
- "name": "Super.x",
- "avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4",
- "profile": "https://segmentfault.com/u/qzuser_584786517d31a",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "wangyu-1999",
- "name": "Wang Yu",
- "avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4",
- "profile": "https://wangyu-1999.github.io/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "felixonmars",
- "name": "Felix Yan",
- "avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4",
- "profile": "https://felixc.at/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "lynettelopez",
- "name": "Lynette Lopez",
- "avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4",
- "profile": "https://github.com/lynettelopez",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "Zheaoli",
- "name": "Manjusaka",
- "avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4",
- "profile": "http://manjusaka.itscoder.com/",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "sudongyuer",
- "name": "Frozen FIsh",
- "avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4",
- "profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular",
- "contributions": [
- "code"
- ]
- },
- {
- "login": "MuhammedFaraz",
- "name": "Mohammed Faraz",
- "avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4",
- "profile": "https://github.com/MuhammedFaraz",
- "contributions": [
- "doc"
- ]
- },
- {
- "login": "Pranav4399",
- "name": "Pranav Sriram ",
- "avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4",
- "profile": "https://pranavsriram.dev/",
- "contributions": [
- "code"
- ]
- }
- ]
-}
diff --git a/.cz-config.js b/.cz-config.js
deleted file mode 100644
index a0608d5af..000000000
--- a/.cz-config.js
+++ /dev/null
@@ -1,66 +0,0 @@
-module.exports = {
- types: [
- { value: 'feat', name: 'feat 🍄: add new features' },
- { value: 'fix', name: 'fix 🐛: fix bug' },
- { value: 'docs', name: 'docs 📄: modify documentation, comments' },
- {
- value: 'refactor',
- name: 'refactor 🎸: code refactoring, pay attention to distinguish it from features and fixes',
- },
- { value: 'perf', name: 'perf ⚡: improve performance' },
- { value: 'test', name: 'test 👀: add a test' },
- {
- value: 'tool',
- name: 'tool 🚗: Development tool changes (build, scaffolding tools, etc.)',
- },
- {
- value: 'style',
- name: 'style ✂: Modifications to code formatting do not affect logic',
- },
- { value: 'revert', name: 'revert 🌝: version rollback' },
- {
- value: 'editor',
- name: 'editor 🔧: editor configuration modification',
- },
- { value: 'update', name: 'update ⬆: third-party library upgrade' },
- ],
- scopes: [
- { name: 'selection' },
- { name: 'edgeless' },
- { name: 'point' },
- { name: 'group' },
- { name: 'page' },
- { name: 'component' },
- { name: 'config' },
- { name: 'others' },
- ],
-
- // it needs to match the value for field type. Eg.: 'fix'
- /*
-scopeOverrides: {
- fix: [
- {name: 'merge'},
- {name: 'style'}
- ]
-
-},
-*/
- // override the messages, de faults are as follows
- messages: {
- type: 'Choose a type of your submission:',
- scope: 'Choose a scope (optional):',
- // used if allowCustomScopes is true
- customScope: 'Denote the SCOPE of this change:',
- subject: 'Brief description:\n',
- body: 'Detailed description, use "|" newline (optional):\n',
- breaking: 'Incompatibility specification (optional):\n',
- footer: 'Associate closed issues, for example: #31, #34 (optional):\n',
- confirmCommit: 'Are you sure to commit?',
- },
-
- allowCustomScopes: true,
- allowBreakingChanges: ['Added', 'Repair'],
-
- // limit subject length
- subjectLimit: 100,
-};
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 9b7352176..000000000
--- a/.editorconfig
+++ /dev/null
@@ -1,13 +0,0 @@
-# Editor configuration, see http://editorconfig.org
-root = true
-
-[*]
-charset = utf-8
-indent_style = space
-indent_size = 4
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-[*.md]
-max_line_length = off
-trim_trailing_whitespace = false
diff --git a/.env b/.env
deleted file mode 100644
index 63e14bc84..000000000
--- a/.env
+++ /dev/null
@@ -1,5 +0,0 @@
-# use for download icon from figma
-
-FIGMA_TOKEN
-NODE_ENV
-AFFINE_FEATURE_FLAG_TOKEN
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 000000000..ce7b73ee9
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,14 @@
+// https://eslint.org/docs/latest/user-guide/configuring
+// "off" or 0 - turn the rule off
+// "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code)
+// "error" or 2 - turn the rule on as an error (exit code will be 1)
+
+/** @type { import('eslint').Linter.Config } */
+module.exports = {
+ extends: ['plugin:prettier/recommended'],
+ rules: {
+ 'prettier/prettier': 'warn',
+ },
+
+ reportUnusedDisableDirectives: true,
+};
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 527de2b1b..000000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,295 +0,0 @@
-{
- "root": true,
- "ignorePatterns": ["**/*"],
- "plugins": ["@nrwl/nx", "react", "filename-rules", "import", "prettier"],
- "parserOptions": {
- "project": ["./tsconfig.base.json"]
- },
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {
- "prettier/prettier": "warn",
- "@nrwl/nx/enforce-module-boundaries": [
- "error",
- {
- "enforceBuildableLibDependency": true,
- "allow": [],
- "depConstraints": [
- {
- "sourceTag": "library:utils",
- "onlyDependOnLibsWithTags": ["library:utils"]
- },
- {
- "sourceTag": "datasource:jwt",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "datasource:remote-kv",
- "datasource:jwt-rpc"
- ]
- },
- {
- "sourceTag": "datasource:db-services",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "datasource:jwt"
- ]
- },
- {
- "sourceTag": "datasource:hooks",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "datasource:jwt",
- "datasource:db-services"
- ]
- },
- {
- "sourceTag": "datasource:http",
- "onlyDependOnLibsWithTags": ["library:utils"]
- },
- {
- "sourceTag": "datasource:state",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "library:feature-flags",
- "datasource:http",
- "datasource:jwt"
- ]
- },
- {
- "sourceTag": "components:common",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "components:icons",
- "components:ui",
- "library:feature-flags"
- ]
- },
- {
- "sourceTag": "components:editor-core",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "library:feature-flags",
- "datasource:db-services",
- "datasource:state",
- "datasource:commands",
- "datasource:jwt",
- "components:ui",
- "components:common",
- "components:icons"
- ]
- },
- {
- "sourceTag": "components:editor-blocks",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "library:feature-flags",
- "components:common",
- "components:editor-core",
- "framework:editor",
- "datasource:db-services",
- "components:ui",
- "components:icons"
- ]
- },
- {
- "sourceTag": "components:editor-plugins",
- "onlyDependOnLibsWithTags": [
- "library:utils",
- "components:common",
- "components:editor-core",
- "framework:editor",
- "components:editor-blocks",
- "datasource:db-services",
- "components:ui",
- "components:icons",
- "library:feature-flags",
- "datasource:i18n"
- ]
- },
- {
- "sourceTag": "components:ui",
- "onlyDependOnLibsWithTags": [
- "components:icons",
- "library:utils"
- ]
- },
- {
- "sourceTag": "framework:editor",
- "onlyDependOnLibsWithTags": [
- "components:editor-core"
- ]
- },
- {
- "sourceTag": "*",
- "onlyDependOnLibsWithTags": ["*"]
- }
- ],
- "allowCircularSelfDependency": false
- }
- ],
- "filename-rules/match": [
- "warn",
- {
- ".tsx": "PascalCase",
- ".ts": "kebab-case",
- ".json": "kebab-case",
- "": "kebab-case"
- }
- ],
- "no-restricted-imports": [
- "error",
- {
- "patterns": [
- {
- "group": ["lodash"],
- "message": "Forbid direct import of lodash, use @toeverything/utils"
- },
- {
- "group": ["lodash-es"],
- "message": "Forbid direct import of lodash-es, use @toeverything/utils"
- },
- {
- "group": ["@mui/material", "@mui/material/*"],
- "message": "Forbid direct import of @mui/material, use @toeverything/components/ui"
- }
- ]
- }
- ],
- "@typescript-eslint/naming-convention": [
- "warn",
- {
- "selector": [
- "property",
- "parameterProperty",
- "accessor",
- "enumMember"
- ],
- "format": ["strictCamelCase"]
- },
- {
- "selector": ["property", "accessor"],
- "modifiers": ["private"],
- "format": ["strictCamelCase"],
- "leadingUnderscore": "require"
- },
- {
- "selector": ["method"],
- "modifiers": ["public"],
- "format": ["strictCamelCase"]
- },
- // Private methods
- {
- "selector": ["method"],
- "modifiers": ["private"],
- "format": ["strictCamelCase"],
- "leadingUnderscore": "require"
- },
- {
- "selector": ["method"],
- "modifiers": ["protected"],
- "format": ["strictCamelCase"],
- "leadingUnderscore": "require"
- },
- // Top Level Methods
- // const func: Function
- // allow PascalCase for react components
- {
- "selector": ["variable"],
- "modifiers": ["global"],
- "types": ["function"],
- "format": ["strictCamelCase", "StrictPascalCase"],
- "leadingUnderscore": "allow"
- },
- // function something() { }
- {
- "selector": ["function"],
- "modifiers": ["global"],
- "format": ["strictCamelCase"],
- "leadingUnderscore": "require"
- },
- // export const func: Function
- {
- "selector": ["variable"],
- "modifiers": ["exported"],
- "types": ["function"],
- "format": ["strictCamelCase", "StrictPascalCase"],
- "leadingUnderscore": "forbid"
- },
- // export function something() { }
- {
- "selector": ["function"],
- "modifiers": ["exported"],
- "format": ["strictCamelCase", "StrictPascalCase"],
- "leadingUnderscore": "forbid"
- },
- // Top Level Variables
- {
- "selector": ["variable"],
- "modifiers": ["global", "const"],
- "types": ["boolean", "string", "number"],
- "format": ["UPPER_CASE"]
- },
- {
- "selector": ["variable"],
- "modifiers": ["global"],
- "format": ["strictCamelCase"],
- "leadingUnderscore": "require"
- },
- {
- "selector": ["variable"],
- "modifiers": ["exported"],
- "format": ["strictCamelCase"]
- },
- // types, enums
- {
- "selector": "typeLike",
- "format": ["PascalCase"]
- },
- {
- "selector": "variableLike",
- "format": ["strictCamelCase"]
- }
- ],
- "react/self-closing-comp": "warn",
- "no-restricted-syntax": [
- "warn",
- {
- "selector": ":matches(PropertyDefinition)[accessibility!='private'][accessibility!='protected'][key.name!='constructor']",
- "message": "Use private instead, please implement getter or setXxx for external read and write requirements"
- }
- ]
- }
- },
- {
- "files": ["index.tsx", "index.*.tsx"],
- "rules": {
- "filename-rules/match": "off"
- }
- },
- {
- "files": ["*.ts", "*.tsx"],
- "extends": ["plugin:@nrwl/nx/typescript"],
- "rules": {
- "prefer-const": "warn",
- "no-console": ["warn", { "allow": ["warn", "error"] }],
- "@typescript-eslint/ban-ts-comment": "warn",
- "@typescript-eslint/no-empty-interface": "warn",
- "@typescript-eslint/no-empty-function": "warn",
- // https://github.com/nrwl/nx/issues/10445
- "react/jsx-key": "error",
- "import/no-default-export": "warn",
- "import/no-duplicates": "warn",
- "max-lines": [
- "warn",
- { "max": 300, "skipComments": true, "skipBlankLines": true }
- ]
- }
- },
- {
- "files": ["*.js", "*.jsx"],
- "extends": ["plugin:@nrwl/nx/javascript"],
- "rules": {}
- }
- ]
-}
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
deleted file mode 100644
index 22f66396d..000000000
--- a/.github/CODEOWNERS
+++ /dev/null
@@ -1,47 +0,0 @@
-# About code owners
-
-# You can use a CODEOWNERS file to define individuals or teams that are responsible for code in a repository.
-
-# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
-
-
-# =================
-# configs
-# =================
-
-.* @darkskygit
-babel.config.json @darkskygit
-nx.json @darkskygit
-tsconfig.base.json @darkskygit
-**/project.json @darkskygit
-**/tsconfig.json @darkskygit
-**/tsconfig.*.json @darkskygit
-**/.babelrc @darkskygit
-**/babel.config.js @darkskygit
-**/.* @darkskygit
-
-# =================
-# components
-# =================
-
-# editor-core
-libs/components/editor-core @lawvs
-
-# editor-blocks
-
-# group block
-libs/components/editor-blocks/src/blocks/group @lawvs
-# todo block
-libs/components/editor-blocks/src/blocks/todo @lawvs
-
-libs/framework/virgo @SaikaSakura
-
-# =================
-# datasource
-# =================
-
-# feature flags
-libs/datasource/feature-flags @lawvs
-
-# jwt
-libs/datasource/jwt @darkskygit
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report-alpha.yml
similarity index 89%
rename from .github/ISSUE_TEMPLATE/bug-report.yml
rename to .github/ISSUE_TEMPLATE/bug-report-alpha.yml
index ba592b331..599cf75af 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report-alpha.yml
@@ -1,7 +1,7 @@
-name: 🐛 Bug report
-description: Report a reproducible bug or regression
+name: 🐛 Bug report (Alpha)
+description: "Report a reproducible bug or regression for https://pathfinder.affine.pro"
title: "[bug]: "
-labels: ["bug"]
+labels: ["bug", "alpha"]
body:
- type: markdown
attributes:
@@ -43,4 +43,3 @@ body:
attributes:
label: Additional context
placeholder: Add any other context about the problem here.
-
diff --git a/.github/ISSUE_TEMPLATE/bug-report-pre-alpha.yml b/.github/ISSUE_TEMPLATE/bug-report-pre-alpha.yml
new file mode 100644
index 000000000..a75e3db6c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report-pre-alpha.yml
@@ -0,0 +1,45 @@
+name: 🐛 Bug report (Pre-Alpha)
+description: "Report a reproducible bug or regression for https://livedemo.affine.pro"
+title: "[bug]: "
+labels: ["bug", "pre-alpha"]
+body:
+ - type: markdown
+ attributes:
+ value: Thanks for taking the time to fill out this bug report!
+ - type: input
+ id: description
+ attributes:
+ label: Describe the bug
+ placeholder: A clear and concise description of what the bug is.
+ - type: textarea
+ id: reproduce
+ attributes:
+ label: To Reproduce
+ placeholder: "Steps to reproduce the behavior\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error"
+ validations:
+ required: true
+ - type: textarea
+ id: screenshots
+ attributes:
+ label: Screenshots
+ placeholder: If applicable, add screenshots to help explain your problem.
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ placeholder: A clear and concise description of what you expected to happen.
+ - type: input
+ id: platform
+ attributes:
+ label: Platform
+ placeholder: e.g. MacOS, Windows10...
+ - type: input
+ id: browser
+ attributes:
+ label: Browser
+ placeholder: e.g. Chrome, Safari
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional context
+ placeholder: Add any other context about the problem here.
diff --git a/.github/deployment/Caddyfile-affine b/.github/deployment/Caddyfile-affine
deleted file mode 100644
index b6db6009e..000000000
--- a/.github/deployment/Caddyfile-affine
+++ /dev/null
@@ -1,28 +0,0 @@
-:3000 {
- root /* ./dist
-
- file_server {
- precompressed br
- }
-
- encode {
- zstd
- gzip 9
- }
-
- @notStatic {
- not path /*.css
- not path /*.js
- not path /*.png
- not path /*.jpg
- not path /*.svg
- not path /*.ttf
- not path /*.eot
- not path /*.woff
- not path /*.woff2
- }
-
- handle @notStatic {
- try_files {path} /index.html
- }
-}
diff --git a/.github/deployment/Caddyfile-lisa b/.github/deployment/Caddyfile-lisa
deleted file mode 100644
index 172d92f0a..000000000
--- a/.github/deployment/Caddyfile-lisa
+++ /dev/null
@@ -1,39 +0,0 @@
-:80 {
- reverse_proxy /api/* keck:3001
- @websockets {
- path /collaboration/*
- }
- reverse_proxy @websockets keck:3000
- reverse_proxy /* lisa:3001 {
- header_up Host lisa:3001
- }
-}
-
-http://lisa:3001 {
- root /* ./dist
-
- file_server {
- precompressed br
- }
-
- encode {
- zstd
- gzip 9
- }
-
- @notStatic {
- not path /*.css
- not path /*.js
- not path /*.png
- not path /*.jpg
- not path /*.svg
- not path /*.ttf
- not path /*.eot
- not path /*.woff
- not path /*.woff2
- }
-
- handle @notStatic {
- try_files {path} /index.html
- }
-}
diff --git a/.github/deployment/Caddyfile-venus b/.github/deployment/Caddyfile-venus
deleted file mode 100644
index 7c05ada7a..000000000
--- a/.github/deployment/Caddyfile-venus
+++ /dev/null
@@ -1,28 +0,0 @@
-:80 {
- root /* ./dist
-
- file_server {
- precompressed br
- }
-
- encode {
- zstd
- gzip 9
- }
-
- @notStatic {
- not path /*.css
- not path /*.js
- not path /*.png
- not path /*.jpg
- not path /*.svg
- not path /*.ttf
- not path /*.eot
- not path /*.woff
- not path /*.woff2
- }
-
- handle @notStatic {
- try_files {path} /index.html
- }
-}
diff --git a/.github/deployment/Dockerfile b/.github/deployment/Dockerfile
new file mode 100644
index 000000000..0e5512413
--- /dev/null
+++ b/.github/deployment/Dockerfile
@@ -0,0 +1,5 @@
+FROM nginx:alpine
+
+COPY ./packages/app/out /usr/share/nginx/html
+
+COPY ./.github/deployment/nginx.conf /etc/nginx/conf.d/default.conf
diff --git a/.github/deployment/Dockerfile-affine b/.github/deployment/Dockerfile-affine
deleted file mode 100644
index b3d1fb367..000000000
--- a/.github/deployment/Dockerfile-affine
+++ /dev/null
@@ -1,23 +0,0 @@
-FROM node:16-alpine as builder
-ARG AFFINE_EMBED_HEADER
-WORKDIR /app
-RUN apk add git && npm i -g pnpm@7
-COPY . .
-# RUN apk add g++ make python3 git libpng-dev
-RUN --mount=type=cache,target=/app/node_modules,rw,sharing=private pnpm i --frozen-lockfile --store=node_modules/.pnpm-store --filter "!ligo-virgo-e2e" --filter "!keck" --filter "!venus" && pnpm run build:local --skip-nx-cache
-
-FROM node:16-alpine as relocate
-WORKDIR /app
-COPY --from=builder /app/dist/apps/ligo-virgo ./dist
-COPY --from=builder /app/.github/deployment/Caddyfile-affine ./Caddyfile
-RUN rm ./dist/*.txt
-
-# =============
-# AFFiNE image
-# =============
-FROM caddy:2.4.6-alpine as AFFiNE
-WORKDIR /app
-COPY --from=relocate /app .
-
-EXPOSE 3000
-CMD ["caddy", "run"]
diff --git a/.github/deployment/Dockerfile-affine-keck b/.github/deployment/Dockerfile-affine-keck
deleted file mode 100644
index 75d02dcac..000000000
--- a/.github/deployment/Dockerfile-affine-keck
+++ /dev/null
@@ -1,22 +0,0 @@
-FROM node:16-alpine as builder
-WORKDIR /app
-RUN apk add git && npm i -g pnpm@7
-COPY . .
-# RUN apk add g++ make python3 git libpng-dev
-RUN --mount=type=cache,target=/app/node_modules,rw,sharing=private pnpm i --frozen-lockfile --store=node_modules/.pnpm-store --filter "!ligo-virgo-e2e" --filter "!keck" --filter "!venus" && pnpm run build:local-keck --skip-nx-cache
-
-FROM node:16-alpine as relocate
-WORKDIR /app
-COPY --from=builder /app/dist/apps/ligo-virgo ./dist
-COPY --from=builder /app/.github/deployment/Caddyfile-affine ./Caddyfile
-RUN rm ./dist/*.txt
-
-# =============
-# AFFiNE image
-# =============
-FROM caddy:2.4.6-alpine as AFFiNE
-WORKDIR /app
-COPY --from=relocate /app .
-
-EXPOSE 3000
-CMD ["caddy", "run"]
diff --git a/.github/deployment/Dockerfile-affine-multiplatform b/.github/deployment/Dockerfile-affine-multiplatform
deleted file mode 100644
index e005ca3ff..000000000
--- a/.github/deployment/Dockerfile-affine-multiplatform
+++ /dev/null
@@ -1,13 +0,0 @@
-ARG BASE_IMAGE=localhost:5000/toeverything/relocate:latest
-
-FROM ${BASE_IMAGE} as relocate
-
-# =============
-# AFFiNE image
-# =============
-FROM caddy:2.4.6-alpine as AFFiNE
-WORKDIR /app
-COPY --from=relocate /app .
-
-EXPOSE 3000
-CMD ["caddy", "run"]
diff --git a/.github/deployment/Dockerfile-keck b/.github/deployment/Dockerfile-keck
deleted file mode 100644
index 7b611c27b..000000000
--- a/.github/deployment/Dockerfile-keck
+++ /dev/null
@@ -1,33 +0,0 @@
-FROM node:16-alpine as builder
-WORKDIR /app
-RUN apk add g++ make python3 git libpng-dev && npm i -g pnpm@7
-COPY . .
-RUN --mount=type=cache,target=/app/node_modules,rw,sharing=private pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:keck
-
-FROM node:16-alpine as node_modules
-WORKDIR /app
-COPY --from=builder /app/dist/apps/keck .
-COPY --from=builder /app/apps/keck/package.prod.json ./package.json
-RUN npm i
-
-# =============
-# keck image
-# =============
-FROM node:16-alpine as keck
-WORKDIR /app
-COPY --from=node_modules /app .
-
-ENV FIREBASE_ACCOUNT=
-ENV FIREBASE_CERT=
-ENV FIREBASE_PROJECT=
-ENV HOST=0.0.0.0
-ENV PORT=3000
-
-# coding
-ENV TZ 'Asia/Shanghai'
-ENV LANG en_US.UTF-8
-ENV LANGUAGE en_US:en
-ENV LC_ALL en_US.UTF-8
-
-EXPOSE 3000
-CMD ["node", "main.js"]
diff --git a/.github/deployment/Dockerfile-lisa b/.github/deployment/Dockerfile-lisa
deleted file mode 100644
index 4f80f6eba..000000000
--- a/.github/deployment/Dockerfile-lisa
+++ /dev/null
@@ -1,22 +0,0 @@
-FROM node:16-alpine as builder
-WORKDIR /app
-RUN apk add git && npm i -g pnpm@7
-COPY . .
-# RUN apk add g++ make python3 git libpng-dev
-RUN --mount=type=cache,target=/app/node_modules,rw,sharing=private pnpm i --frozen-lockfile --store=node_modules/.pnpm-store --filter "!ligo-virgo-e2e" --filter "!keck" --filter "!venus" && pnpm run build
-
-FROM node:16-alpine as relocate
-WORKDIR /app
-COPY --from=builder /app/dist/apps/ligo-virgo ./dist
-COPY --from=builder /app/.github/deployment/Caddyfile-lisa ./Caddyfile
-RUN rm ./dist/*.txt
-
-# =============
-# lisa image
-# =============
-FROM caddy:2.4.6-alpine as lisa
-WORKDIR /app
-COPY --from=relocate /app .
-
-EXPOSE 3000
-CMD ["caddy", "run"]
\ No newline at end of file
diff --git a/.github/deployment/Dockerfile-venus b/.github/deployment/Dockerfile-venus
deleted file mode 100644
index 92a2bb830..000000000
--- a/.github/deployment/Dockerfile-venus
+++ /dev/null
@@ -1,21 +0,0 @@
-FROM node:16-alpine as builder
-WORKDIR /app
-RUN apk add g++ make python3 git libpng-dev && npm i -g pnpm@7
-COPY . .
-RUN --mount=type=cache,target=/app/node_modules,rw,sharing=private pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus
-
-FROM node:16-alpine as relocate
-WORKDIR /app
-COPY --from=builder /app/dist/apps/venus ./dist
-COPY --from=builder /app/.github/deployment/Caddyfile-venus ./Caddyfile
-RUN rm ./dist/*.txt
-
-# =============
-# venus image
-# =============
-FROM caddy:2.4.6-alpine as venus
-WORKDIR /app
-COPY --from=relocate /app .
-
-EXPOSE 80
-CMD ["caddy", "run"]
\ No newline at end of file
diff --git a/.github/deployment/nginx.conf b/.github/deployment/nginx.conf
new file mode 100644
index 000000000..7441f6f8f
--- /dev/null
+++ b/.github/deployment/nginx.conf
@@ -0,0 +1,29 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name localhost;
+
+ #access_log /var/log/nginx/host.access.log main;
+
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+
+ location /_next/static {
+ # 7 days
+ add_header Cache-Control "max-age=86400";
+ }
+
+ location / {
+ try_files $uri $uri.html /$uri /index.html
+ add_header Cache-Control "no-cache";
+ }
+
+ #error_page 404 /404.html;
+
+ # redirect server error pages to the static page /50x.html
+ #
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+}
diff --git a/.github/env/.env.e2e b/.github/env/.env.e2e
deleted file mode 100644
index 0c627a684..000000000
--- a/.github/env/.env.e2e
+++ /dev/null
@@ -1,2 +0,0 @@
-NX_LOCAL=true
-NX_E2E=true
\ No newline at end of file
diff --git a/.github/env/.env.local-dev b/.github/env/.env.local-dev
deleted file mode 100644
index 7471e5ffd..000000000
--- a/.github/env/.env.local-dev
+++ /dev/null
@@ -1 +0,0 @@
-NX_LOCAL=true
diff --git a/.github/env/.env.local-keck b/.github/env/.env.local-keck
deleted file mode 100644
index 7ea68ace7..000000000
--- a/.github/env/.env.local-keck
+++ /dev/null
@@ -1,2 +0,0 @@
-NX_LOCAL=true
-NX_KECK=true
\ No newline at end of file
diff --git a/.github/workflows/affine-keck.yml b/.github/workflows/affine-keck.yml
deleted file mode 100644
index b52630e0e..000000000
--- a/.github/workflows/affine-keck.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-name: Build AFFiNE-Local-Keck
-
-on:
- workflow_dispatch:
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- REGISTRY: ghcr.io
- NAMESPACE: toeverything
- AFFINE_IMAGE_NAME: AFFiNE
- IMAGE_TAG_LATEST: nightly-keck
- LOCAL_CACHE: localhost:5000/toeverything/relocate:latest
-
-jobs:
- ligo-virgo:
- runs-on: self-hosted
- environment: development
- permissions:
- contents: read
- packages: write
-
- services:
- registry:
- image: registry:2
- ports:
- - 5000:5000
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@v2
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v1
-
- - name: Set up Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@v1
- with:
- driver-opts: network=host
-
- - name: Extract metadata (tags, labels) for Docker (AFFiNE-Local)
- id: meta_affine
- uses: docker/metadata-action@v4
- with:
- images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }}
- tags: ${{ env.IMAGE_TAG_LATEST }}
-
- - name: Build and push Docker image (AFFINE-Local)
- uses: docker/build-push-action@v3
- with:
- context: .
- push: true
- file: ./.github/deployment/Dockerfile-affine-keck
- tags: ${{ env.LOCAL_CACHE }}
- target: AFFiNE
-
- - name: Build and push Docker image (AFFINE-Local)
- uses: docker/build-push-action@v3
- with:
- context: .
- platforms: linux/amd64,linux/arm64/v8
- file: ./.github/deployment/Dockerfile-affine-multiplatform
- push: ${{ github.ref == 'refs/heads/master' && true || false }}
- tags: ${{ steps.meta_affine.outputs.tags }}
- labels: ${{ steps.meta_affine.outputs.labels }}
- target: AFFiNE
- build-args: |
- BASE_IMAGE=${{ env.LOCAL_CACHE }}
diff --git a/.github/workflows/affine.yml b/.github/workflows/affine.yml
deleted file mode 100644
index 412516f17..000000000
--- a/.github/workflows/affine.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-name: Build AFFiNE-Local
-
-on:
- push:
- branches: [master]
- # pull_request:
- # branches: [master]
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- REGISTRY: ghcr.io
- NAMESPACE: toeverything
- AFFINE_IMAGE_NAME: AFFiNE
- IMAGE_TAG_LATEST: nightly-latest
- LOCAL_CACHE: localhost:5000/toeverything/relocate:latest
-
-jobs:
- ligo-virgo:
- runs-on: self-hosted
- environment: development
- permissions:
- contents: read
- packages: write
-
- services:
- registry:
- image: registry:2
- ports:
- - 5000:5000
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@v2
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v1
-
- - name: Set up Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@v1
- with:
- driver-opts: network=host
-
- - name: Extract metadata (tags, labels) for Docker (AFFiNE-Local)
- id: meta_affine
- uses: docker/metadata-action@v4
- with:
- images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }}
- tags: ${{ env.IMAGE_TAG_LATEST }}
-
- - name: Build and push Docker image (AFFINE-Local)
- uses: docker/build-push-action@v3
- with:
- context: .
- push: true
- file: ./.github/deployment/Dockerfile-affine
- tags: ${{ env.LOCAL_CACHE }}
- target: AFFiNE
-
- - name: Build and push Docker image (AFFINE-Local)
- uses: docker/build-push-action@v3
- with:
- context: .
- platforms: linux/amd64,linux/arm64/v8
- file: ./.github/deployment/Dockerfile-affine-multiplatform
- push: ${{ github.ref == 'refs/heads/master' && true || false }}
- tags: ${{ steps.meta_affine.outputs.tags }}
- labels: ${{ steps.meta_affine.outputs.labels }}
- target: AFFiNE
- build-args: |
- BASE_IMAGE=${{ env.LOCAL_CACHE }}
diff --git a/.github/workflows/build-community.yml b/.github/workflows/build-community.yml
new file mode 100644
index 000000000..2291513cd
--- /dev/null
+++ b/.github/workflows/build-community.yml
@@ -0,0 +1,120 @@
+name: Build Pathfinder Community Version
+
+on:
+ push:
+ branches: [pathfinder]
+ pull_request:
+ branches: [pathfinder]
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+# See https://docs.github.com/en/actions/using-jobs/using-concurrency
+concurrency:
+ # The concurrency group contains the workflow name and the branch name for
+ # pull requests or the commit hash for any other events.
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Lint and Build
+ runs-on: self-hosted
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 'latest'
+
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
+ with:
+ node-version: 18.x
+ cache: 'pnpm'
+
+ - name: Restore cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ .next/cache
+ # Generate a new cache whenever packages or source files change.
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
+ # If source files changed but packages didn't, rebuild from a prior cache.
+ restore-keys: |
+ ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
+
+ - name: Install dependencies
+ run: pnpm install
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
+
+ - name: Lint
+ run: |
+ pnpm lint --max-warnings=0
+
+ # - name: Test
+ # run: pnpm test
+
+ - name: Build
+ run: pnpm build
+
+ - name: Export
+ run: pnpm export
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v3
+ with:
+ path: ./packages/app/out
+
+ push_to_registry:
+ # See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
+ name: Push Docker image to Docker Hub
+ if: github.ref == 'refs/heads/pathfinder'
+ runs-on: ubuntu-latest
+ needs: build
+
+ permissions:
+ contents: read
+ packages: write
+
+ env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: 'toeverything/affine-pathfinder-community'
+ IMAGE_TAG: canary-${{ github.sha }}
+ IMAGE_TAG_LATEST: nightly-latest
+
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v2
+
+ - name: Download artifact
+ uses: actions/download-artifact@v3
+ with:
+ name: artifact
+ path: packages/app/out/
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ ${{ env.IMAGE_TAG }}
+ ${{ env.IMAGE_TAG_LATEST }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v3
+ with:
+ context: .
+ push: true
+ file: ./.github/deployment/Dockerfile
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/build-livedemo.yml b/.github/workflows/build-livedemo.yml
new file mode 100644
index 000000000..9cbf01a71
--- /dev/null
+++ b/.github/workflows/build-livedemo.yml
@@ -0,0 +1,122 @@
+name: Build Pathfinder LiveDemo Version
+
+on:
+ push:
+ branches: [pathfinder]
+ pull_request:
+ branches: [pathfinder]
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+# See https://docs.github.com/en/actions/using-jobs/using-concurrency
+concurrency:
+ # The concurrency group contains the workflow name and the branch name for
+ # pull requests or the commit hash for any other events.
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Lint and Build
+ runs-on: self-hosted
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 'latest'
+
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: 18.x
+ registry-url: https://npm.pkg.github.com
+ scope: '@toeverything'
+ cache: 'pnpm'
+
+ - run: node scripts/module-resolve/ci.js
+
+ - name: Restore cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ .next/cache
+ # Generate a new cache whenever packages or source files change.
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
+ # If source files changed but packages didn't, rebuild from a prior cache.
+ restore-keys: |
+ ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
+
+ - name: Install dependencies
+ run: pnpm install --no-frozen-lockfile
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
+
+ - name: Lint
+ run: |
+ pnpm lint --max-warnings=0
+
+ # - name: Test
+ # run: pnpm test
+
+ - name: Build
+ run: pnpm build
+
+ - name: Export
+ run: pnpm export
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v3
+ with:
+ path: ./packages/app/out
+
+ push_to_registry:
+ # See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
+ name: Push Docker image to Docker Hub
+ if: github.ref == 'refs/heads/pathfinder'
+ runs-on: ubuntu-latest
+ needs: build
+
+ permissions:
+ contents: read
+ packages: write
+
+ env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: 'toeverything/affine-pathfinder'
+ IMAGE_TAG: canary-${{ github.sha }}
+ IMAGE_TAG_LATEST: nightly-latest
+
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v2
+
+ - name: Download artifact
+ uses: actions/download-artifact@v3
+ with:
+ name: artifact
+ path: packages/app/out/
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ ${{ env.IMAGE_TAG }}
+ ${{ env.IMAGE_TAG_LATEST }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v3
+ with:
+ context: .
+ push: true
+ file: ./.github/deployment/Dockerfile
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/build-test-version.yml b/.github/workflows/build-test-version.yml
new file mode 100644
index 000000000..265986417
--- /dev/null
+++ b/.github/workflows/build-test-version.yml
@@ -0,0 +1,123 @@
+name: Build Pathfinder LiveDemo Test Version
+
+on:
+ workflow_dispatch:
+ inputs:
+ tag:
+ description: "Custom Tag. Set nightly-latest will publish to development."
+ required: true
+ type: string
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+# See https://docs.github.com/en/actions/using-jobs/using-concurrency
+concurrency:
+ # The concurrency group contains the workflow name and the branch name for
+ # pull requests or the commit hash for any other events.
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Lint and Build
+ runs-on: self-hosted
+
+ steps:
+ - uses: actions/checkout@v2
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 'latest'
+
+ - name: Use Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: 18.x
+ registry-url: https://npm.pkg.github.com
+ scope: '@toeverything'
+ cache: 'pnpm'
+
+ - run: node scripts/module-resolve/ci.js
+
+ - name: Restore cache
+ uses: actions/cache@v3
+ with:
+ path: |
+ .next/cache
+ # Generate a new cache whenever packages or source files change.
+ key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
+ # If source files changed but packages didn't, rebuild from a prior cache.
+ restore-keys: |
+ ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
+
+ - name: Install dependencies
+ run: pnpm install --no-frozen-lockfile
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
+
+ - name: Lint
+ run: |
+ pnpm lint --max-warnings=0
+
+ # - name: Test
+ # run: pnpm test
+
+ - name: Build
+ run: pnpm build
+
+ - name: Export
+ run: pnpm export
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v3
+ with:
+ path: ./packages/app/out
+
+ push_to_registry:
+ # See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images
+ name: Push Docker image to Docker Hub
+ runs-on: ubuntu-latest
+ needs: build
+
+ permissions:
+ contents: read
+ packages: write
+
+ env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: 'toeverything/affine-pathfinder-testing'
+ IMAGE_TAG: canary-${{ github.sha }}
+ IMAGE_TAG_LATEST: nightly-latest
+
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v2
+
+ - name: Download artifact
+ uses: actions/download-artifact@v3
+ with:
+ name: artifact
+ path: packages/app/out/
+
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ ${{ env.IMAGE_TAG }}
+ ${{ inputs.tag }}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v3
+ with:
+ context: .
+ push: true
+ file: ./.github/deployment/Dockerfile
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
deleted file mode 100644
index a49efb4d9..000000000
--- a/.github/workflows/check.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: standard check
-
-on:
- push:
- branches: [ "develop", "master" ]
- pull_request:
- branches: [ "develop", "master" ]
-
-concurrency:
- group: ${{ github.workflow }}-${{ github.event.number || github.ref }}
- cancel-in-progress: true
-
-jobs:
- main:
- name: Nx Cloud - Main Job
- uses: ./.github/workflows/nx-cloud-main.yml
- secrets: inherit
- with:
- main-branch-name: develop
-# number-of-agents: 2
- parallel-commands: |
- pnpm exec nx-cloud record -- pnpm exec nx format:check
- pnpm e2e:ci ${{ github.ref == 'refs/heads/develop' && '--record' || '' }}
- pnpm exec nx affected --target=lint --parallel=2 --exclude=components-common,keck,theme
- pnpm exec nx affected --target=build --parallel=2
-# parallel-commands-on-agents: |
-# pnpm exec nx affected --target=lint --parallel=2 --exclude=components-common,keck,theme
-# pnpm exec nx affected --target=build --parallel=2
-
-# agents:
-# name: Nx Cloud - Agents
-# uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.6
-# with:
-# number-of-agents: 2
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index 75ff3a801..000000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
- push:
- branches: [ "develop", master ]
- pull_request:
- # The branches below must be a subset of the branches above
- branches: [ "develop" ]
- schedule:
- - cron: '27 1 * * 0'
-
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: [ 'javascript' ]
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
-
- # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
- # queries: security-extended,security-and-quality
-
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
-
- # ℹ️ Command-line programs to run using the OS shell.
- # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-
- # If the Autobuild fails above, remove it and uncomment the following three lines.
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
-
- # - run: |
- # echo "Run, Build Application using script"
- # ./location_of_script_within_repo/buildscript.sh
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..eb0072f19
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,69 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: 'CodeQL'
+
+on:
+ push:
+ branches: [pathfinder]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [pathfinder]
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: ['javascript']
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+
+ # If the Autobuild fails above, remove it and uncomment the following three lines.
+ # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
+
+ # - run: |
+ # echo "Run, Build Application using script"
+ # ./location_of_script_within_repo/buildscript.sh
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/keck.yml b/.github/workflows/keck.yml
deleted file mode 100644
index 32e1d5605..000000000
--- a/.github/workflows/keck.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: Build Keck
-
-on:
- workflow_dispatch:
- push:
- branches: [master]
- paths:
- - 'apps/keck/**'
- - '.github/deployment'
- - '.github/workflows/keck.yml'
- # pull_request:
- # branches: [master]
- # paths:
- # - 'apps/keck/**'
- # - '.github/deployment'
- # - '.github/workflows/keck.yml'
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- REGISTRY: ghcr.io
- NAMESPACE: toeverything
- KECK_IMAGE_NAME: keck
- IMAGE_TAG: canary-${{ github.sha }}
- IMAGE_TAG_LATEST: nightly-latest
-
-jobs:
- ligo-virgo:
- runs-on: self-hosted
- environment: development
- permissions:
- contents: read
- packages: write
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Extract metadata (tags, labels) for Docker (keck)
- id: meta_keck
- uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
- with:
- images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.KECK_IMAGE_NAME }}
- tags: |
- ${{ env.IMAGE_TAG }}
- ${{ env.IMAGE_TAG_LATEST }}
-
- - name: Build and push Docker image (keck)
- uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
- with:
- context: .
- file: ./.github/deployment/Dockerfile-keck
- push: ${{ github.ref == 'refs/heads/field' && true || false }}
- tags: ${{ steps.meta_keck.outputs.tags }}
- labels: ${{ steps.meta_keck.outputs.labels }}
- target: keck
diff --git a/.github/workflows/languages-download.yml b/.github/workflows/languages-download.yml
deleted file mode 100644
index ee57fdf5b..000000000
--- a/.github/workflows/languages-download.yml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: Download Languages Resources
-
-on:
- schedule:
- - cron: "0 0 * * 5" # At 00:00(UTC) on Friday.
- workflow_dispatch:
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-# This action need write permission to create pull requests
-# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
-permissions:
- contents: write
- pull-requests: write
-
-jobs:
- main:
- strategy:
- matrix:
- node-version: [18]
- os: [ubuntu-latest]
- runs-on: ${{ matrix.os }}
-
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Use pnpm
- uses: pnpm/action-setup@v2
- with:
- version: 7
-
- - name: Use Node.js ${{ matrix.node-version }}
- # https://github.com/actions/setup-node
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
- cache: 'pnpm'
-
- - name: Install node modules
- run: pnpm install
-
- - name: Sync Languages
- working-directory: ./libs/datasource/i18n
- run: pnpm run download-resources
- env:
- TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
-
- - name: Push Branch
- id: push
- run: |
- git add libs/datasource/i18n
- # Do not proceed if there are no file differences
- COMMIT=$(git rev-parse --verify origin/$TARGET_BRANCH || echo HEAD)
- FILES_CHANGED=$(git diff-index --name-only --cached $COMMIT | wc -l)
- if [[ "$FILES_CHANGED" = "0" ]]; then
- echo "No file changes detected."
- echo "::set-output name=skipPR::true"
- exit 0
- fi
- git config user.name 'github-actions[bot]'
- git config user.email 'github-actions[bot]@users.noreply.github.com'
- git commit --message 'feat(i18n): new translations' --no-verify
- git remote set-url origin "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY"
- git push --force origin HEAD:$TARGET_BRANCH
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- TARGET_BRANCH: bot/new-translations
-
- - name: Get current date
- id: date
- run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
-
- # see https://github.com/repo-sync/pull-request
- - name: Create Pull Request
- if: steps.push.outputs.skipPR != 'true'
- uses: repo-sync/pull-request@v2
- with:
- source_branch: 'bot/new-translations' # If blank, default: triggered branch
- destination_branch: "develop"
- pr_title: Update i18n (${{ steps.date.outputs.date }}) # Title of pull request
- pr_label: 'data,bot' # Comma-separated list (no spaces)
- github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/languages-sync.yml b/.github/workflows/languages-sync.yml
deleted file mode 100644
index 33b0b79e9..000000000
--- a/.github/workflows/languages-sync.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-name: Languages Sync
-
-on:
- push:
- branches: [ "develop", "master" ]
- paths:
- - 'libs/datasource/i18n/**'
- - '.github/workflows/languages-sync.yml'
- pull_request:
- branches: [ "develop", "master" ]
- paths:
- - 'libs/datasource/i18n/**'
- - '.github/workflows/languages-sync.yml'
- workflow_dispatch:
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-jobs:
- main:
- strategy:
- matrix:
- node-version: [18]
- os: [ubuntu-latest]
- runs-on: ${{ matrix.os }}
-
- steps:
- - name: Checkout
- uses: actions/checkout@v3
-
- - name: Use pnpm
- uses: pnpm/action-setup@v2
- with:
- version: 7
-
- - name: Use Node.js ${{ matrix.node-version }}
- # https://github.com/actions/setup-node
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
- cache: 'pnpm'
-
- - name: Install node modules
- run: pnpm install
-
- - name: Check Language Key
- if: github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/master'
- working-directory: ./libs/datasource/i18n
- run: pnpm run sync-languages:check
- env:
- TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
-
- - name: Sync Languages
- if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'
- working-directory: ./libs/datasource/i18n
- run: pnpm run sync-languages
- env:
- TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
diff --git a/.github/workflows/lisa.yml b/.github/workflows/lisa.yml
deleted file mode 100644
index f548ddeee..000000000
--- a/.github/workflows/lisa.yml
+++ /dev/null
@@ -1,60 +0,0 @@
-name: Build Lisa
-
-on:
- push:
- branches: [master]
- # pull_request:
- # branches: [master]
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- REGISTRY: ghcr.io
- NAMESPACE: toeverything
- LISA_IMAGE_NAME: lisa
- IMAGE_TAG: canary-${{ github.sha }}
- IMAGE_TAG_LATEST: nightly-latest
-
-jobs:
- ligo-virgo:
- runs-on: self-hosted
- environment: development
- permissions:
- contents: read
- packages: write
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Extract metadata (tags, labels) for Docker (lisa)
- id: meta_lisa
- uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
- with:
- images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.LISA_IMAGE_NAME }}
- tags: |
- ${{ env.IMAGE_TAG }}
- ${{ env.IMAGE_TAG_LATEST }}
-
- - name: Build and push Docker image (lisa)
- uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
- with:
- context: .
- file: ./.github/deployment/Dockerfile-lisa
- push: ${{ github.ref == 'refs/heads/master' && true || false }}
- tags: ${{ steps.meta_lisa.outputs.tags }}
- labels: ${{ steps.meta_lisa.outputs.labels }}
- target: lisa
diff --git a/.github/workflows/livedemo.yml b/.github/workflows/livedemo.yml
deleted file mode 100644
index 9be084345..000000000
--- a/.github/workflows/livedemo.yml
+++ /dev/null
@@ -1,87 +0,0 @@
-name: Build AFFiNE-Livedemo
-
-on:
- push:
- branches: [master]
- # pull_request:
- # branches: [master]
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- REGISTRY: ghcr.io
- NAMESPACE: toeverything
- AFFINE_IMAGE_NAME: LIVEDEMO
- IMAGE_TAG_LATEST: nightly-latest
- LOCAL_CACHE: localhost:5000/toeverything/relocate:latest
-
-jobs:
- ligo-virgo:
- runs-on: self-hosted
- environment: development
- permissions:
- contents: read
- packages: write
-
- services:
- registry:
- image: registry:2
- ports:
- - 5000:5000
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@v2
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v1
-
- - name: Set up Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@v1
- with:
- driver-opts: network=host
-
- - name: Extract metadata (tags, labels) for Docker (AFFiNE-Local)
- id: meta_affine
- uses: docker/metadata-action@v4
- with:
- images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }}
- tags: ${{ env.IMAGE_TAG_LATEST }}
-
- - name: Build and push Docker image (AFFINE-Local)
- uses: docker/build-push-action@v3
- with:
- context: .
- push: true
- file: ./.github/deployment/Dockerfile-affine
- tags: ${{ env.LOCAL_CACHE }}
- target: AFFiNE
- build-args: |
- AFFINE_EMBED_HEADER=${{ secrets.AFFINE_EMBED_HEADER }}
-
- - name: Build and push Docker image (AFFINE-Local)
- uses: docker/build-push-action@v3
- with:
- context: .
- platforms: linux/amd64,linux/arm64/v8
- file: ./.github/deployment/Dockerfile-affine-multiplatform
- push: ${{ github.ref == 'refs/heads/master' && true || false }}
- tags: ${{ steps.meta_affine.outputs.tags }}
- labels: ${{ steps.meta_affine.outputs.labels }}
- target: AFFiNE
- build-args: |
- BASE_IMAGE=${{ env.LOCAL_CACHE }}
diff --git a/.github/workflows/nx-cloud-main.yml b/.github/workflows/nx-cloud-main.yml
deleted file mode 100644
index 4e729845d..000000000
--- a/.github/workflows/nx-cloud-main.yml
+++ /dev/null
@@ -1,300 +0,0 @@
-name: Nx Cloud Main
-
-on:
- workflow_call:
- secrets:
- NX_CLOUD_ACCESS_TOKEN:
- required: false
- NX_CLOUD_AUTH_TOKEN:
- required: false
- NX_CYPRESS_KEY:
- required: false
- inputs:
- number-of-agents:
- required: false
- type: number
- environment-variables:
- required: false
- type: string
- init-commands:
- required: false
- type: string
- final-commands:
- required: false
- type: string
- parallel-commands:
- required: false
- type: string
- parallel-commands-on-agents:
- required: false
- type: string
- node-version:
- required: false
- type: string
- yarn-version:
- required: false
- type: string
- npm-version:
- required: false
- type: string
- pnpm-version:
- required: false
- type: string
- install-commands:
- required: false
- type: string
- main-branch-name:
- required: false
- type: string
- default: main
- runs-on:
- required: false
- type: string
- default: ubuntu-latest
- # We needed this input in order to be able to configure out integration tests for this repo, it is not documented
- # so as to not cause confusion/add noise, but technically any consumer of the workflow can use it if they want to.
- working-directory:
- required: false
- type: string
-
-env:
- NX_CLOUD_DISTRIBUTED_EXECUTION: true
- NX_CLOUD_DISTRIBUTED_EXECUTION_AGENT_COUNT: ${{ inputs.number-of-agents }}
- NX_BRANCH: ${{ github.event.number || github.ref_name }}
- NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
- NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }}
- CYPRESS_RECORD_KEY: ${{ secrets.NX_CYPRESS_KEY }}
-
-jobs:
- main:
- runs-on: ${{ inputs.runs-on }}
- # The name of the job which will invoke this one is expected to be "Nx Cloud - Main Job", and whatever we call this will be appended
- # to that one after a forward slash, so we keep this one intentionally short to produce "Nx Cloud - Main Job / Run" in the Github UI
- name: Run
- defaults:
- run:
- working-directory: ${{ inputs.working-directory || github.workspace }}
- # Specify shell to help normalize across different operating systems
- shell: bash
- steps:
- - uses: actions/checkout@v2
- name: Checkout [Pull Request]
- if: ${{ github.event_name == 'pull_request' }}
- with:
- # By default, PRs will be checked-out based on the Merge Commit, but we want the actual branch HEAD.
- ref: ${{ github.event.pull_request.head.sha }}
- # We need to fetch all branches and commits so that Nx affected has a base to compare against.
- fetch-depth: 0
-
- - uses: actions/checkout@v2
- name: Checkout [Default Branch]
- if: ${{ github.event_name != 'pull_request' }}
- with:
- # We need to fetch all branches and commits so that Nx affected has a base to compare against.
- fetch-depth: 0
-
- - name: Derive appropriate SHAs for base and head for `nx affected` commands
- uses: nrwl/nx-set-shas@v2
- with:
- main-branch-name: ${{ inputs.main-branch-name }}
-
- - name: Detect package manager
- id: package_manager
- shell: bash
- run: |
- echo "::set-output name=name::$([[ -f ./yarn.lock ]] && echo "yarn" || ([[ -f ./pnpm-lock.yaml ]] && echo "pnpm") || echo "npm")"
- # Set node/npm/yarn versions using volta, with optional overrides provided by the consumer
- - uses: volta-cli/action@v3
- with:
- node-version: "${{ inputs.node-version }}"
- npm-version: "${{ inputs.npm-version }}"
- yarn-version: "${{ inputs.yarn-version }}"
-
- # Install pnpm with exact version provided by consumer or fallback to latest
- - name: Install PNPM
- if: steps.package_manager.outputs.name == 'pnpm'
- uses: pnpm/action-setup@v2.2.1
- with:
- version: ${{ inputs.pnpm-version || 'latest' }}
-
- - name: Print node/npm/yarn versions
- id: versions
- run: |
- node_ver=$( node --version )
- yarn_ver=$( yarn --version || true )
- pnpm_ver=$( pnpm --version || true )
- echo "Node: ${node_ver:1}"
- echo "NPM: $( npm --version )"
- if [[ $yarn_ver != '' ]]; then echo "Yarn: $yarn_ver"; fi
- if [[ $pnpm_ver != '' ]]; then echo "PNPM: $pnpm_ver"; fi
- echo "::set-output name=node_version::${node_ver:1}"
- - name: Use the node_modules cache if available [npm]
- if: steps.package_manager.outputs.name == 'npm'
- uses: actions/cache@v2
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-
- - name: Use the node_modules cache if available [pnpm]
- if: steps.package_manager.outputs.name == 'pnpm'
- uses: actions/cache@v2
- with:
- path: ~/.pnpm-store
- key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-
- - name: Get yarn cache directory path
- if: steps.package_manager.outputs.name == 'yarn'
- id: yarn-cache-dir-path
- run: echo "::set-output name=dir::$(yarn cache dir)"
-
- - name: Use the node_modules cache if available [yarn]
- if: steps.package_manager.outputs.name == 'yarn'
- uses: actions/cache@v2
- with:
- path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
- key: ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-yarn-${{ hashFiles('**/yarn.lock') }}
- restore-keys: |
- ${{ runner.os }}-node-${{ steps.versions.outputs.node_version }}-yarn-
- - name: Process environment-variables
- if: ${{ inputs.environment-variables != '' }}
- uses: actions/github-script@v6
- env:
- ENV_VARS: ${{ inputs.environment-variables }}
- with:
- script: |
- const { appendFileSync } = require('fs');
- // trim spaces and escape quotes
- const cleanStr = str => str
- .trim()
- .replaceAll(/`/g, "\`");
- // parse variable to correct type
- const parseStr = str =>
- str === 'true' || str === 'TRUE'
- ? true
- : str === 'false' || str === 'FALSE'
- ? false
- : isNaN(str)
- ? str
- : parseFloat(str);
- const varsStr = process.env.ENV_VARS || '';
- const vars = varsStr
- .split('\n')
- .map(variable => variable.trim())
- .filter(variable => variable.indexOf('=') > 0)
- .map(variable => ({
- name: cleanStr(variable.split('=')[0]),
- value: cleanStr(variable.slice(variable.indexOf('=') + 1))
- }));
- for (const v of vars) {
- console.log(`Appending environment variable \`${v.name}\` with value \`${v.value}\` to ${process.env.GITHUB_ENV}`);
- appendFileSync(process.env.GITHUB_ENV, `${v.name}=${parseStr(v.value)}\n`);
- }
- - name: Run any configured install-commands
- if: ${{ inputs.install-commands != '' }}
- run: |
- ${{ inputs.install-commands }}
- - name: Install dependencies
- if: ${{ inputs.install-commands == '' }}
- run: |
- if [ "${{ steps.package_manager.outputs.name == 'yarn' }}" == "true" ]; then
- echo "Running yarn install --frozen-lockfile"
- yarn install --frozen-lockfile
- elif [ "${{ steps.package_manager.outputs.name == 'pnpm' }}" == "true" ]; then
- echo "Running pnpm install --frozen-lockfile"
- pnpm install --frozen-lockfile
- else
- echo "Running npm ci"
- npm ci
- fi
- # An unfortunate side-effect of the way reusable workflows work is that by the time they are pulled into the "caller"
- # repo, they are effectively completely embedded in that context. This means that we cannot reference any files which
- # are local to this repo which defines the workflow, and we therefore need to work around this by embedding the contents
- # of the shell utilities for executing commands into the workflow directly.
- - name: Create command utils
- uses: actions/github-script@v6
- with:
- script: |
- const { writeFileSync } = require('fs');
- const runCommandsInParallelScript = `
- # Extract the provided commands from the stringified JSON array.
- IFS=$'\n' read -d '' -a userCommands < <((jq -c -r '.[]') <<<"$1")
- # Invoke the provided commands in parallel and collect their exit codes.
- pids=()
- for userCommand in "\${userCommands[@]}"; do
- eval "$userCommand" & pids+=($!)
- done
- # If any one of the invoked commands exited with a non-zero exit code, exit the whole thing with code 1.
- for pid in \${pids[*]}; do
- if ! wait $pid; then
- exit 1
- fi
- done
- # All the invoked commands must have exited with code zero.
- exit 0
- `;
- writeFileSync('./.github/workflows/run-commands-in-parallel.sh', runCommandsInParallelScript);
- - name: Prepare command utils
- # We need to escape the workspace path to be consistent cross-platform: https://github.com/actions/runner/issues/1066
- run: chmod +x ${GITHUB_WORKSPACE//\\//}/.github/workflows/run-commands-in-parallel.sh
-
- - name: Initialize the Nx Cloud distributed CI run
- run: npx nx-cloud start-ci-run
-
- # The good thing about the multi-line string input for sequential commands is that we can simply forward it on as is to the bash shell and it will behave
- # how we want it to in terms of quote escaping, variable assignment etc
- - name: Run any configured init-commands sequentially
- if: ${{ inputs.init-commands != '' }}
- run: |
- ${{ inputs.init-commands }}
- - name: Process parallel commands configuration
- uses: actions/github-script@v6
- id: parallel_commands_config
- env:
- PARALLEL_COMMANDS: ${{ inputs.parallel-commands }}
- PARALLEL_COMMANDS_ON_AGENTS: ${{ inputs.parallel-commands-on-agents }}
- with:
- # For the ones configured for main, explicitly set NX_CLOUD_DISTRIBUTED_EXECUTION to false, taking into account commands chained with &&
- # within the strings. In order to properly escape single quotes we need to do some manual replacing and escaping so that the commands
- # are forwarded onto the run-commands-in-parallel.sh script appropriately.
- script: |
- const parallelCommandsOnMainStr = process.env.PARALLEL_COMMANDS || '';
- const parallelCommandsOnAgentsStr = process.env.PARALLEL_COMMANDS_ON_AGENTS || '';
- const parallelCommandsOnMain = parallelCommandsOnMainStr
- .split('\n')
- .map(command => command.trim())
- .filter(command => command.length > 0)
- .map(s => s.replace(/'/g, '%27'));
- const parallelCommandsOnAgents = parallelCommandsOnAgentsStr
- .split('\n')
- .map(command => command.trim())
- .filter(command => command.length > 0)
- .map(s => s.replace(/'/g, '%27'));
- const formattedArrayOfCommands = [
- ...parallelCommandsOnMain.map(s => s
- .split(' && ')
- .map(s => `NX_CLOUD_DISTRIBUTED_EXECUTION=false ${s}`)
- .join(' && ')
- ),
- ...parallelCommandsOnAgents,
- ];
- const stringifiedEncodedArrayOfCommands = JSON.stringify(formattedArrayOfCommands)
- .replace(/%27/g, "'\\''");
- return stringifiedEncodedArrayOfCommands
- result-encoding: string
-
- - name: Run any configured parallel commands on main and agent jobs
- # We need to escape the workspace path to be consistent cross-platform: https://github.com/actions/runner/issues/1066
- run: ${GITHUB_WORKSPACE//\\//}/.github/workflows/run-commands-in-parallel.sh '${{ steps.parallel_commands_config.outputs.result }}'
-
- # The good thing about the multi-line string input for sequential commands is that we can simply forward it on as is to the bash shell and it will behave
- # how we want it to in terms of quote escaping, variable assignment etc
- - name: Run any configured final-commands sequentially
- if: ${{ inputs.final-commands != '' }}
- run: |
- ${{ inputs.final-commands }}
- - name: Stop all running agents for this CI run
- # It's important that we always run this step, otherwise in the case of any failures in preceding non-Nx steps, the agents will keep running and waste billable minutes
- if: ${{ always() }}
- run: npx nx-cloud stop-all-agents
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 000000000..521cc093d
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,40 @@
+name: E2E & Unit Tests
+on:
+ push:
+ branches: [pathfinder]
+ pull_request:
+ branches: [pathfinder]
+jobs:
+ test:
+ timeout-minutes: 60
+ runs-on: self-hosted
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - uses: pnpm/action-setup@v2
+ with:
+ version: 'latest'
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ registry-url: https://npm.pkg.github.com
+ scope: '@toeverything'
+ cache: 'pnpm'
+
+ - run: node scripts/module-resolve/ci.js
+
+ - name: Install dependencies
+ run: pnpm install --no-frozen-lockfile
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }}
+
+ - name: Install Playwright Browsers
+ run: npx playwright install --with-deps
+
+ - name: Run E2E tests
+ run: pnpm run test:e2e
+
+ - name: Run Unit tests
+ run: pnpm run test:unit
diff --git a/.github/workflows/venus.yml b/.github/workflows/venus.yml
deleted file mode 100644
index 699cf44dd..000000000
--- a/.github/workflows/venus.yml
+++ /dev/null
@@ -1,67 +0,0 @@
-name: Build Venus
-
-on:
- push:
- branches: [master]
- paths:
- - 'apps/venus/**'
- - '.github/deployment'
- - '.github/workflows/venus.yml'
- # pull_request:
- # branches: [master]
- # paths:
- # - 'apps/venus/**'
- # - '.github/workflows/venus.yml'
-
-# Cancels all previous workflow runs for pull requests that have not completed.
-# See https://docs.github.com/en/actions/using-jobs/using-concurrency
-concurrency:
- # The concurrency group contains the workflow name and the branch name for
- # pull requests or the commit hash for any other events.
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- REGISTRY: ghcr.io
- NAMESPACE: toeverything
- VENUS_IMAGE_NAME: venus
- IMAGE_TAG: canary-${{ github.sha }}
- IMAGE_TAG_LATEST: nightly-latest
-
-jobs:
- ligo-virgo:
- runs-on: self-hosted
- environment: development
- permissions:
- contents: read
- packages: write
-
- steps:
- - name: Checkout
- uses: actions/checkout@v2
-
- - name: Log in to the Container registry
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
- with:
- registry: ${{ env.REGISTRY }}
- username: ${{ github.actor }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Extract metadata (tags, labels) for Docker (venus)
- id: meta_venus
- uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
- with:
- images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.VENUS_IMAGE_NAME }}
- tags: |
- ${{ env.IMAGE_TAG }}
- ${{ env.IMAGE_TAG_LATEST }}
-
- - name: Build and push Docker image (venus)
- uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
- with:
- context: .
- file: ./.github/deployment/Dockerfile-venus
- push: ${{ github.ref == 'refs/heads/master' && true || false }}
- tags: ${{ steps.meta_venus.outputs.tags }}
- labels: ${{ steps.meta_venus.outputs.labels }}
- target: venus
diff --git a/.gitignore b/.gitignore
index 7b5e08b2c..2d049c3c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,11 @@ Thumbs.db
*.env.local
*.local.env
.history
+
+.next
+out/
+
+module-resolve.js
+/test-results/
+/playwright-report/
+/playwright/.cache/
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index 54bcfe98f..000000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
-
-# npx lint-staged
-
-pnpm run format:ci
diff --git a/.husky/pre-push b/.husky/pre-push
deleted file mode 100755
index 9c7674d58..000000000
--- a/.husky/pre-push
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
-
-# Show just the current branch in Git
-# See https://stackoverflow.com/questions/1417957/show-just-the-current-branch-in-git/1418022#1418022
-current_branch=$(git rev-parse --abbrev-ref HEAD)
-
-default_branch="master"
-
-if test $current_branch != $default_branch; then
- exit 0
-fi
-
-pnpm run build:check
diff --git a/.npmrc b/.npmrc
deleted file mode 100644
index c74aa6aa6..000000000
--- a/.npmrc
+++ /dev/null
@@ -1,3 +0,0 @@
-registry=https://registry.npmjs.org
-engine-strict=true
-auto-install-peers=true
\ No newline at end of file
diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs
new file mode 100644
index 000000000..4873d9492
--- /dev/null
+++ b/.pnpmfile.cjs
@@ -0,0 +1,28 @@
+const fs = require('fs');
+
+function getCustomize() {
+ const customed = fs.existsSync('./module-resolve.js');
+ if (!customed) {
+ return null;
+ }
+ const script = require('./module-resolve.js');
+ return script && script.resolve;
+}
+
+const customize = getCustomize();
+
+function readPackage(pkg) {
+ if (!customize) {
+ return pkg;
+ }
+
+ const customizedPkg = customize(pkg);
+
+ return customizedPkg;
+}
+
+module.exports = {
+ hooks: {
+ readPackage,
+ },
+};
diff --git a/.prettierignore b/.prettierignore
deleted file mode 100644
index c02f12d53..000000000
--- a/.prettierignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Add files here to ignore them from prettier formatting
-
-/dist
-/coverage
-pnpm-lock.yaml
-
-
-# Automatically generated from Figma
-libs/components/icons/src/auto-icons
-libs/components/common/src/lib/icon
-
-# DevOps
-.github/**
diff --git a/.prettierrc b/.prettierrc
index babd1d0f7..b85f39b22 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,6 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
- "tabWidth": 4,
+ "tabWidth": 2,
"arrowParens": "avoid"
}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
deleted file mode 100644
index 1a9637994..000000000
--- a/.vscode/extensions.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "recommendations": [
- "mikestead.dotenv",
- "esbenp.prettier-vscode",
- "visualstudioexptteam.vscodeintellicode",
- "nrwl.angular-console",
- "firsttris.vscode-jest-runner",
- "dbaeumer.vscode-eslint",
- "streetsidesoftware.code-spell-checker"
- ]
-}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 18f956c61..df31aeb4b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,47 +1,7 @@
{
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "editor.formatOnSave": true,
- "editor.formatOnSaveMode": "file",
- "editor.codeActionsOnSave": ["source.fixAll", "source.organizeImports"],
- "prettier.prettierPath": "./node_modules/prettier",
- "cSpell.words": [
- "aboutus",
- "AUTOINCREMENT",
- "Backlinks",
- "blockdb",
- "booktitle",
- "Buildx",
- "Cascader",
- "clsx",
- "cssmodule",
- "datasource",
- "fflate",
- "fstore",
- "groq",
- "howpublished",
- "immer",
- "inbook",
- "incollection",
- "inproceedings",
- "Kanban",
- "keyval",
- "ligo",
- "livedemo",
- "lozad",
- "mastersthesis",
- "nrwl",
- "phdthesis",
- "pnpm",
- "reindex",
- "ROOTNODE",
- "techreport",
- "tldr",
- "tldraw",
- "tldtaw",
- "toeverything",
- "Unstyled",
- "unversioned",
- "uuidv",
- "webm"
- ]
+ "eslint.packageManager": "pnpm",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "editor.formatOnSaveMode": "file",
+ "cSpell.words": ["testid"]
}
diff --git a/README.md b/README.md
index f758b0543..6ac80d826 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,17 @@
-
-
- AFFiNE.PRO
-
+
+
+
+ AFFiNE.PRO
The Next-Gen Collaborative Knowledge Base
-
-Open-source and privacy-first.
-A free replacement for Notion & Miro.
+
+ AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together.
+ Privacy first, open-source, customizable and ready to use - a free replacement for Notion & Miro.
-
-
+
+**Choice of multiple languages** — Thanks to community contributions AFFiNE offers support for multiple languages. If you don't find your language or would like to suggest some changes we welcome your contributions.
+
+## Getting started
+
+⚠️ Please note that AFFiNE is still under active development and is not yet ready for production use. ⚠️
[](https://affine.pro) No installation or registration required! Head over to our website and try it out now.
-Want to deploy it yourself? AFFiNE can run just about anywhere.
-
[AFFiNE Documentation](https://docs.affine.pro/affine/) - More detailed documentation on how to use and develop with AFFiNE
-AFFiNE Communities: [Discord](https://discord.gg/yz6tGVsf5p) | [Telegram](https://t.me/affineworkos) | [Twitter](https://twitter.com/AffineOfficial)
-
-⚠️ Please note that AFFiNE is still under active development and is not yet ready for production use. ⚠️
+[Our official communities](https://docs.affine.pro/affine/community-links/official-communities) - Join our friendly communities for more support and discussions
-[Install AFFiNE with docker](https://docs.affine.pro/affine/getting-started/install-affine-with-docker) - This guide assumes you already have an environment setup running docker
+## Contributing
-## Create your story
+Calling all developers, testers, tech writers and more! Contributions of all types are more than welcome, you can read more in [docs/types-of-contributions.md](docs/types-of-contributions.md). If you are interested in contributing code, read our [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) and feel free to check out our GitHub issues to get stuck in to show us what you’re made of.
-There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together. Privacy first, open-source, customizable and ready to use, built with web technologies to ensure consistency and accessibility on Mac, Windows and Linux. We want your data always to be yours, without any sacrifice to your accessibility. Your data is always stored local first, with full support for real-time collaboration through peer-to-peer technology. We don't think "privacy-first" is a good excuse for not supporting modern web features.
-And when it comes to collaboration, these features are not just necessarily for teams - you can take and insert pictures on your phone, edit them from your desktop, and then share them with your collaborators.
+For **bug reports**, **feature requests** and other **suggestions** you can also [create a new issue](https://github.com/toeverything/AFFiNE/issues/new/choose) and choose the most appropiate template for your feedback.
-### Shape your page
+For **translation** and **language support** you can visit our docs for the [internationalization guide].(https://docs.affine.pro/affine/internationalization/welcome)
-
+Looking for **others ways to contribute** and wondering where to start? Check out the [AFFiNE Ambassador program](https://docs.affine.pro/affine/affine-ambassadors/welcome), we work closely with passionate members of our community and provide them with a wide-range of support and resources.
-### Plan your task
+If you have questions, join us across various [**social platforms**](https://docs.affine.pro/affine/community-links/official-communities) where our friendly community can help provide the answers.
-
+## Thanks
-### Sort your knowledge
-
-
-
-# Contact Us
-
-Feel free to send us an email: contact@toeverything.info
-
-# The Philosophy of AFFiNE
-
-Timothy Berners-Lee once taught us about the idea of the semantic web, where all the data can be interpreted in any form while the "truth" is kept. This gives our best image of an ideal knowledge base by far, that sorting of information, planning of project and goals as well as creating of knowledge can be all together.
-We have witnessed waves of paradigm shift so many times. At first, everything was noted on office-like apps or DSL like LaTeX, then we found todo-list apps and WYSIWYG markdown editors better for writing and planning. Finally, here comes Notion and Miro, who take advantage of the idea of blocks to further liberate our creativity.
-It is all perfect... without waste operations and redundant information. And, we insist that privacy first should always be given by default.
-That's why we are making AFFiNE. Some of the most important features are:
-
-- Transformable
- - Every block can be transformed equally
- - e.g. you can create a todo in Markdown in the text view and then later edit it in the kanban view.
- - Every document can be turned into a whiteboard
- - An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
-- Atomic
- - The basic elements of AFFiNE are blocks, not pages.
- - Blocks can be directly reused and synced between pages.
- - Pages and blocks are searched and organized based on connected graphs, not tree-like paths.
- - Dual-link and semantic search are fully supported.
-- Collaborative and privacy-first
- - Data is always stored locally by default
- - CRDTs are applied so that peer-to-peer collaboration is possible.
-
-We appreciate the ideas of Monday, Airtable, and Notion databases. They have inspired us and shaped our product, helping us get it right when it comes to task management. But we also do things differently. We don't like doing things again and again. It's easy to set a todo with Markdown, but then why do you need to repeat and recreate data for a kanban or other databases. This is the power of AFFiNE. With AFFiNE, every block group has infinite views, for you to keep your single source of data, a single source of truth.
-
-We would like to give special thanks to the innovators and pioneers who greatly inspired us:
-
-- Quip & Notion -- that docs can be organized as blocks
-- Taskade & Monday -- brilliant multi-dimensional tables
-- Height & Linear -- beautiful task management tool
-
-We would also like to give thanks to open-source projects that make affine possible:
+We would also like to give thanks to open-source projects that make AFFiNE possible:
+- [BlockSuite](https://github.com/toeverything/BlockSuite) - AFFiNE is built with and powered by BlockSuite.
- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implementation on state management and data sync.
- [React](https://github.com/facebook/react) -- View layer support and web GUI framework.
- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST.
@@ -137,8 +115,6 @@ We use the following open source projects to help us build a better development
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
-[Some amazing companies](./docs/jobs/summary.md), including AFFiNE are looking for developers! Are you interested in helping build with AFFiNE and/or its partners? Check out some of the latest [jobs available](./docs/jobs/summary.md).
-
# Contributors
@@ -168,7 +144,7 @@ Thanks a lot to the community for providing such powerful and simple libraries,
Svaney 🎨
Guozhu Liu 🎨
fyZheng07 📋 📓
-
CJSS 📖
+
ShortCipher5 📖
JimmFly 💻
Carlos Rafael 💻
@@ -202,8 +178,10 @@ Thanks a lot to the community for providing such powerful and simple libraries,
-# License
+## Jobs
-AFFiNE is distributed under the terms of MIT license.
+Some amazing companies including AFFiNE are looking for developers! Are you interested in helping build with AFFiNE and/or its partners? Check out some of the latest [jobs available](./docs/jobs/summary.md).
+
+## License
See [LICENSE](/LICENSE) for details.
diff --git a/apps/.gitkeep b/apps/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/apps/keck/.eslintrc.json b/apps/keck/.eslintrc.json
deleted file mode 100644
index 5626944bd..000000000
--- a/apps/keck/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/apps/keck/jest.config.ts b/apps/keck/jest.config.ts
deleted file mode 100644
index f514e7b28..000000000
--- a/apps/keck/jest.config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'keck',
- preset: '../../jest.preset.js',
- globals: {
- 'ts-jest': {
- tsconfig: '
/tsconfig.spec.json',
- },
- },
- testEnvironment: 'node',
- transform: {
- '^.+\\.[tj]s$': 'ts-jest',
- },
- moduleFileExtensions: ['ts', 'js', 'html'],
- coverageDirectory: '../../coverage/apps/keck',
-};
diff --git a/apps/keck/package.json b/apps/keck/package.json
deleted file mode 100644
index d960ea769..000000000
--- a/apps/keck/package.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "@toeverything/keck",
- "version": "0.0.1",
- "license": "MIT",
- "author": "DarkSky ",
- "main": "jest.config.ts",
- "dependencies": {
- "authing-js-sdk": "^4.23.35",
- "firebase-admin": "^11.0.1",
- "lib0": "^0.2.52",
- "lru-cache": "^7.13.2",
- "nanoid": "^4.0.0",
- "readable-stream": "^4.1.0",
- "ws": "^8.8.1",
- "y-protocols": "^1.0.5",
- "yjs": "^13.5.41"
- },
- "devDependencies": {
- "@types/readable-stream": "^2.3.13",
- "@types/ws": "^8.5.3"
- }
-}
diff --git a/apps/keck/package.prod.json b/apps/keck/package.prod.json
deleted file mode 100644
index 21d5fbf84..000000000
--- a/apps/keck/package.prod.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "@toeverything/keck",
- "license": "MIT",
- "version": "0.0.1",
- "dependencies": {
- "level": "^8.0.0",
- "level-read-stream": "1.1.0"
- }
-}
diff --git a/apps/keck/project.json b/apps/keck/project.json
deleted file mode 100644
index b58fd3e1a..000000000
--- a/apps/keck/project.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "apps/keck/src",
- "projectType": "application",
- "targets": {
- "build": {
- "executor": "@nrwl/node:webpack",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/apps/keck",
- "main": "apps/keck/src/index.ts",
- "tsConfig": "apps/keck/tsconfig.app.json",
- "assets": []
- },
- "configurations": {
- "production": {
- "optimization": true,
- "extractLicenses": true,
- "inspect": false,
- "fileReplacements": [
- {
- "replace": "apps/keck/src/environments/environment.ts",
- "with": "apps/keck/src/environments/environment.prod.ts"
- }
- ]
- }
- }
- },
- "serve": {
- "executor": "@nrwl/node:node",
- "options": {
- "buildTarget": "keck:build"
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": ["apps/keck/**/*.ts"]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/apps/keck"],
- "options": {
- "jestConfig": "apps/keck/jest.config.ts",
- "passWithNoTests": true
- }
- }
- },
- "tags": []
-}
diff --git a/apps/keck/src/environments/environment.prod.ts b/apps/keck/src/environments/environment.prod.ts
deleted file mode 100644
index da7c84f63..000000000
--- a/apps/keck/src/environments/environment.prod.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const environment = {
- production: true,
-};
diff --git a/apps/keck/src/environments/environment.ts b/apps/keck/src/environments/environment.ts
deleted file mode 100644
index 39644a62e..000000000
--- a/apps/keck/src/environments/environment.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const environment = {
- production: false,
-};
diff --git a/apps/keck/src/index.ts b/apps/keck/src/index.ts
deleted file mode 100644
index d924f1d78..000000000
--- a/apps/keck/src/index.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import WebSocket = require('ws');
-import http = require('http');
-
-// import authing = require('authing-js-sdk');
-import firebaseApp = require('firebase-admin/app');
-import firebaseAuth = require('firebase-admin/auth');
-import LRUCache = require('lru-cache');
-import nanoid = require('nanoid');
-
-import { URL } from 'url';
-import { handleConnection } from './utils';
-
-if (process.env.NODE_ENV !== 'development') {
- firebaseApp.initializeApp({
- credential: firebaseApp.cert({
- clientEmail: process.env.FIREBASE_ACCOUNT,
- privateKey: process.env.FIREBASE_CERT,
- projectId: process.env.FIREBASE_PROJECT,
- }),
- projectId: process.env.FIREBASE_PROJECT,
- });
-}
-
-const _getWorkspace = (path: string) => {
- const [_, part1] = path.split('/collaboration/');
- const [workspace] = part1?.split('/') || [];
- return workspace;
-};
-
-const AFFINE_COMMON_WORKSPACE = 'affine2vin277tcmafwq';
-
-const _checkAuth = async (
- request: http.IncomingMessage,
- response: http.ServerResponse,
- callback: (response: http.OutgoingMessage, workspace: string) => boolean
-) => {
- const url = new URL(request.url, `http://${request.headers.host}`);
- const workspace = _getWorkspace(url.pathname);
- if (process.env.NODE_ENV === 'development') {
- if (workspace) return callback(response, workspace);
- return false;
- }
- try {
- const decodedToken = await firebaseAuth
- .getAuth()
- .verifyIdToken(request.headers.token as string);
- const allowWorkspace = [AFFINE_COMMON_WORKSPACE, decodedToken.uid];
- if (allowWorkspace.includes(workspace)) {
- return callback(response, workspace);
- }
- } catch (error) {
- console.log(error);
- }
- return false;
-};
-
-const HOST = process.env.HOST || 'localhost';
-const PORT = process.env.PORT || 3000;
-
-const _tokens = new LRUCache({
- max: 1024 * 10,
- ttl: 1000 * 60 * 5,
-});
-
-const _server = http.createServer((request, response) => {
- if (
- request.method === 'POST' &&
- typeof request.headers.token === 'string'
- ) {
- _checkAuth(request, response, (response, workspace) => {
- const protocol = nanoid.nanoid(16);
- _tokens.set(protocol, workspace);
- response.end(JSON.stringify({ protocol }));
- return true;
- })
- .then(responded => {
- if (!responded) {
- response.writeHead(401).end();
- }
- })
- .catch(error => {
- console.log(error);
- response.writeHead(401).end();
- });
- return;
- }
- response.writeHead(200, { 'Content-Type': 'text/plain' });
- response.end('okay');
-});
-
-const _websocketServer = new WebSocket.Server({ noServer: true });
-_websocketServer.on('connection', handleConnection);
-
-_server.on('upgrade', (request, socket, head) => {
- // You may check auth of request here..
- // See https://github.com/websockets/ws#client-authentication
- const protocol = request.headers['sec-websocket-protocol'];
- if (typeof protocol === 'string' && _tokens.get(protocol)) {
- _websocketServer.handleUpgrade(request, socket, head, ws => {
- _websocketServer.emit(
- 'connection',
- ws,
- request,
- _tokens.get(protocol)
- );
- });
- } else {
- socket.write('HTTP/1.1 401 Unauthorized');
- socket.destroy();
- }
-});
-
-_server.listen(PORT, () => {
- console.log(`running at '${HOST}' on port ${PORT}`);
-});
diff --git a/apps/keck/src/utils.ts b/apps/keck/src/utils.ts
deleted file mode 100644
index 5e57f6a77..000000000
--- a/apps/keck/src/utils.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-import WebSocket = require('ws');
-import http = require('http');
-import Y = require('yjs');
-import lib0 = require('lib0');
-import awarenessProtocol = require('y-protocols/awareness');
-import syncProtocol = require('y-protocols/sync');
-// import { getPersistenceStorage } from './persistence';
-
-const { encoding, decoding, mutex, map } = lib0;
-
-const wsReadyStateConnecting = 0;
-const wsReadyStateOpen = 1;
-
-// disable gc when using snapshots!
-const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0';
-
-type Persistence =
- | ((arg0: string, arg1: WSSharedDoc) => Promise)
- | undefined;
-const persistence: Persistence = null; // getPersistenceStorage('./affine');
-
-const docs: Map = new Map();
-
-const messageSync = 0;
-const messageAwareness = 1;
-// const messageAuth = 2
-
-const updateHandler = (update: Uint8Array, origin: any, doc: WSSharedDoc) => {
- const encoder = encoding.createEncoder();
- encoding.writeVarUint(encoder, messageSync);
- syncProtocol.writeUpdate(encoder, update);
- const message = encoding.toUint8Array(encoder);
- doc.conns.forEach((_, conn) => send(doc, conn, message));
-};
-
-type AwarenessEvent = {
- added: Array;
- updated: Array;
- removed: Array;
-};
-
-export class WSSharedDoc extends Y.Doc {
- name: string;
- mux: lib0.mutex.mutex;
- conns: Map;
- awareness: awarenessProtocol.Awareness;
- /**
- * @param {string} name
- */
- constructor(name: string) {
- super({ gc: gcEnabled });
- this.name = name;
- this.mux = mutex.createMutex();
- /**
- * Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed
- * @type {Map>}
- */
- this.conns = new Map();
- /**
- * @type {awarenessProtocol.Awareness}
- */
- this.awareness = new awarenessProtocol.Awareness(this);
- this.awareness.setLocalState(null);
-
- const awarenessChangeHandler = (
- { added, updated, removed }: AwarenessEvent,
- conn: object | null
- ) => {
- const changedClients = added.concat(updated, removed);
- if (conn !== null) {
- const connControlledIds: Set = this.conns.get(conn);
- if (connControlledIds !== undefined) {
- added.forEach(clientId => {
- connControlledIds.add(clientId);
- });
- removed.forEach(clientId => {
- connControlledIds.delete(clientId);
- });
- }
- }
- // broadcast awareness update
- const encoder = encoding.createEncoder();
- encoding.writeVarUint(encoder, messageAwareness);
- encoding.writeVarUint8Array(
- encoder,
- awarenessProtocol.encodeAwarenessUpdate(
- this.awareness,
- changedClients
- )
- );
- const buff = encoding.toUint8Array(encoder);
- this.conns.forEach((_, c) => {
- send(this, c, buff);
- });
- };
- this.awareness.on('update', awarenessChangeHandler);
- this.on('update', updateHandler);
- }
-}
-
-// Gets a Y.Doc by name, whether in memory or on disk
-const getYDoc = (docname: string, gc = true): WSSharedDoc =>
- map.setIfUndefined(docs, docname, () => {
- const doc = new WSSharedDoc(docname);
- doc.gc = gc;
- if (persistence !== null) {
- persistence(docname, doc);
- }
- docs.set(docname, doc);
- return doc;
- });
-
-const messageListener = (conn: any, doc: WSSharedDoc, message: Uint8Array) => {
- try {
- const encoder = encoding.createEncoder();
- const decoder = decoding.createDecoder(message);
- const messageType = decoding.readVarUint(decoder);
- switch (messageType) {
- case messageSync:
- encoding.writeVarUint(encoder, messageSync);
- syncProtocol.readSyncMessage(decoder, encoder, doc, null);
- if (encoding.length(encoder) > 1) {
- send(doc, conn, encoding.toUint8Array(encoder));
- }
- break;
- case messageAwareness: {
- awarenessProtocol.applyAwarenessUpdate(
- doc.awareness,
- decoding.readVarUint8Array(decoder),
- conn
- );
- break;
- }
- }
- } catch (err) {
- console.error(err);
- doc.emit('error', [err]);
- }
-};
-
-const closeConn = (doc: WSSharedDoc, conn: any) => {
- if (doc.conns.has(conn)) {
- const controlledIds: Set = doc.conns.get(conn);
- doc.conns.delete(conn);
- awarenessProtocol.removeAwarenessStates(
- doc.awareness,
- Array.from(controlledIds),
- null
- );
- if (doc.conns.size === 0 && persistence !== null) {
- // if persisted, we store state and destroy ydocument
- persistence(doc.name, doc).then(() => {
- doc.destroy();
- });
- docs.delete(doc.name);
- }
- }
- conn.close();
-};
-
-const send = (doc: WSSharedDoc, conn: any, m: Uint8Array) => {
- if (
- conn.readyState !== wsReadyStateConnecting &&
- conn.readyState !== wsReadyStateOpen
- ) {
- closeConn(doc, conn);
- }
- try {
- conn.send(m, (/** @param {any} err */ err: any) => {
- err != null && closeConn(doc, conn);
- });
- } catch (e) {
- closeConn(doc, conn);
- }
-};
-
-export const handleConnection = (
- socket: WebSocket.WebSocket,
- request: http.IncomingMessage,
- docName: string
-) => {
- const gc = true;
- socket.binaryType = 'arraybuffer';
- // get doc, initialize if it does not exist yet
- const doc = getYDoc(docName, gc);
- doc.conns.set(socket, new Set());
- // listen and reply to events
- socket.on('message', (message: ArrayBuffer) =>
- messageListener(socket, doc, new Uint8Array(message))
- );
-
- // Check if connection is still alive
- let pongReceived = true;
- const pingInterval = setInterval(() => {
- if (!pongReceived) {
- if (doc.conns.has(socket)) {
- closeConn(doc, socket);
- }
- clearInterval(pingInterval);
- } else if (doc.conns.has(socket)) {
- pongReceived = false;
- try {
- socket.ping();
- } catch (e) {
- closeConn(doc, socket);
- clearInterval(pingInterval);
- }
- }
- }, 30 * 1000);
- socket.on('close', () => {
- closeConn(doc, socket);
- clearInterval(pingInterval);
- });
- socket.on('pong', () => {
- pongReceived = true;
- });
- // put the following in a variables in a block so the interval handlers don't keep in in
- // scope
- {
- // send sync step 1
- const encoder = encoding.createEncoder();
- encoding.writeVarUint(encoder, messageSync);
- syncProtocol.writeSyncStep1(encoder, doc);
- send(doc, socket, encoding.toUint8Array(encoder));
- const awarenessStates = doc.awareness.getStates();
- if (awarenessStates.size > 0) {
- const encoder = encoding.createEncoder();
- encoding.writeVarUint(encoder, messageAwareness);
- encoding.writeVarUint8Array(
- encoder,
- awarenessProtocol.encodeAwarenessUpdate(
- doc.awareness,
- Array.from(awarenessStates.keys())
- )
- );
- send(doc, socket, encoding.toUint8Array(encoder));
- }
- }
-};
diff --git a/apps/keck/src/utils.ts.bak b/apps/keck/src/utils.ts.bak
deleted file mode 100644
index ad8d5840b..000000000
--- a/apps/keck/src/utils.ts.bak
+++ /dev/null
@@ -1,139 +0,0 @@
-import WebSocket = require('ws');
-import http = require('http');
-import Y = require('yjs');
-import lib0 = require('lib0');
-import syncProtocol = require('y-protocols/sync');
-
-const { encoding, decoding, map } = lib0;
-
-const wsReadyStateConnecting = 0;
-const wsReadyStateOpen = 1;
-
-// disable gc when using snapshots!
-const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0';
-
-const docs: Map = new Map();
-
-const messageSync = 0;
-
-const updateHandler = (update: Uint8Array, origin: any, doc: WSSharedDoc) => {
- const encoder = encoding.createEncoder();
- encoding.writeVarUint(encoder, messageSync);
- syncProtocol.writeUpdate(encoder, update);
- const message = encoding.toUint8Array(encoder);
- doc.conns.forEach((_, conn) => send(doc, conn, message));
-};
-export class WSSharedDoc extends Y.Doc {
- name: string;
- conns: Map;
-
- constructor(name: string) {
- super({ gc: gcEnabled });
- this.name = name;
- // Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed
- this.conns = new Map();
-
- this.on('update', updateHandler);
- }
-}
-
-// Gets a Y.Doc by name, whether in memory or on disk
-const getYDoc = (docname: string, gc = true): WSSharedDoc =>
- map.setIfUndefined(docs, docname, () => {
- const doc = new WSSharedDoc(docname);
- doc.gc = gc;
- docs.set(docname, doc);
- return doc;
- });
-
-const closeConn = (doc: WSSharedDoc, conn: any) => {
- if (doc.conns.has(conn)) {
- doc.conns.delete(conn);
- }
- conn.close();
-};
-
-const send = (doc: WSSharedDoc, conn: any, m: Uint8Array) => {
- if (
- conn.readyState !== wsReadyStateConnecting &&
- conn.readyState !== wsReadyStateOpen
- ) {
- closeConn(doc, conn);
- }
- try {
- conn.send(m, (/** @param {any} err */ err: any) => {
- err != null && closeConn(doc, conn);
- });
- } catch (e) {
- closeConn(doc, conn);
- }
-};
-
-export const handleConnection = (
- socket: WebSocket.WebSocket,
- request: http.IncomingMessage,
- docName: string
-) => {
- const gc = true;
- socket.binaryType = 'arraybuffer';
- // get doc, initialize if it does not exist yet
- const doc = getYDoc(docName, gc);
- doc.conns.set(socket, new Set());
- // listen and reply to events
- socket.on('message', (message: ArrayBuffer) => {
- try {
- const encoder = encoding.createEncoder();
- const decoder = decoding.createDecoder(new Uint8Array(message));
- const messageType = decoding.readVarUint(decoder);
- switch (messageType) {
- case messageSync:
- encoding.writeVarUint(encoder, messageSync);
- syncProtocol.readSyncMessage(decoder, encoder, doc, null);
- if (encoding.length(encoder) > 1) {
- send(doc, socket, encoding.toUint8Array(encoder));
- }
- break;
- }
- } catch (err) {
- console.error(err);
- doc.emit('error', [err]);
- }
- });
-
- // Check if connection is still alive
- let pongReceived = true;
- const pingInterval = setInterval(() => {
- if (!pongReceived) {
- if (doc.conns.has(socket)) {
- closeConn(doc, socket);
- }
- clearInterval(pingInterval);
- } else if (doc.conns.has(socket)) {
- pongReceived = false;
- try {
- socket.ping();
- } catch (e) {
- closeConn(doc, socket);
- clearInterval(pingInterval);
- }
- }
- }, 30 * 1000);
- socket.on('close', () => {
- closeConn(doc, socket);
- clearInterval(pingInterval);
- });
- socket.on('pong', () => {
- pongReceived = true;
- });
- // put the following in a variables in a block so the interval handlers don't keep in in
- // scope
- {
- // send sync step 1
- const encoder = encoding.createEncoder();
- encoding.writeVarUint(encoder, messageSync);
- console.log('sync step 0', encoding.toUint8Array(encoder));
- syncProtocol.writeSyncStep1(encoder, doc);
- send(doc, socket, encoding.toUint8Array(encoder));
- console.log('sync step 1 sent', encoding.toUint8Array(encoder));
- }
-};
diff --git a/apps/keck/tsconfig.app.json b/apps/keck/tsconfig.app.json
deleted file mode 100644
index 326599fe0..000000000
--- a/apps/keck/tsconfig.app.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "module": "commonjs",
- "types": ["node"]
- },
- "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"],
- "include": ["**/*.ts"]
-}
diff --git a/apps/keck/tsconfig.json b/apps/keck/tsconfig.json
deleted file mode 100644
index e23d75a22..000000000
--- a/apps/keck/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.app.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/apps/keck/tsconfig.spec.json b/apps/keck/tsconfig.spec.json
deleted file mode 100644
index 5591a03c7..000000000
--- a/apps/keck/tsconfig.spec.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
-}
diff --git a/apps/ligo-virgo-e2e/.eslintrc.json b/apps/ligo-virgo-e2e/.eslintrc.json
deleted file mode 100644
index 84a4be82b..000000000
--- a/apps/ligo-virgo-e2e/.eslintrc.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["src/plugins/index.js"],
- "rules": {
- "@typescript-eslint/no-var-requires": "off",
- "no-undef": "off"
- }
- }
- ]
-}
diff --git a/apps/ligo-virgo-e2e/cypress.config.ts b/apps/ligo-virgo-e2e/cypress.config.ts
deleted file mode 100644
index 46bcce890..000000000
--- a/apps/ligo-virgo-e2e/cypress.config.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { defineConfig } from 'cypress';
-
-module.exports = defineConfig({
- projectId: 'r1wrqr',
- e2e: {
- supportFile: './src/support/index.ts',
- specPattern: './src/integration',
- setupNodeEvents(on, config) {
- // implement node event listeners here
- },
- },
- fileServerFolder: '.',
- fixturesFolder: './src/fixtures',
- video: false,
- // videosFolder: '../../dist/cypress/apps/ligo-virgo-e2e/videos',
- screenshotsFolder: '../../dist/cypress/apps/ligo-virgo-e2e/screenshots',
- chromeWebSecurity: false,
-});
diff --git a/apps/ligo-virgo-e2e/package.json b/apps/ligo-virgo-e2e/package.json
deleted file mode 100644
index 03d495c5a..000000000
--- a/apps/ligo-virgo-e2e/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "ligo-virgo-e2e",
- "version": "1.0.0",
- "license": "MIT",
- "description": "",
- "author": "AFFiNE ",
- "dependencies": {},
- "devDependencies": {
- "cypress": "^10.4.0"
- }
-}
diff --git a/apps/ligo-virgo-e2e/project.json b/apps/ligo-virgo-e2e/project.json
deleted file mode 100644
index 3ddcf56e1..000000000
--- a/apps/ligo-virgo-e2e/project.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "apps/ligo-virgo-e2e/src",
- "projectType": "application",
- "targets": {
- "e2e": {
- "executor": "@nrwl/cypress:cypress",
- "options": {
- "cypressConfig": "apps/ligo-virgo-e2e/cypress.config.ts",
- "devServerTarget": "ligo-virgo:serve"
- },
- "configurations": {
- "production": {
- "devServerTarget": "ligo-virgo:serve:production"
- }
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": ["apps/ligo-virgo-e2e/**/*.{js,ts}"]
- }
- }
- },
- "tags": [],
- "implicitDependencies": ["ligo-virgo"]
-}
diff --git a/apps/ligo-virgo-e2e/src/fixtures/example.json b/apps/ligo-virgo-e2e/src/fixtures/example.json
deleted file mode 100644
index b132a94fe..000000000
--- a/apps/ligo-virgo-e2e/src/fixtures/example.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "Using fixtures to represent data",
- "email": "hello@cypress.io"
-}
diff --git a/apps/ligo-virgo-e2e/src/integration/app.spec.ts b/apps/ligo-virgo-e2e/src/integration/app.spec.ts
deleted file mode 100644
index 520bd9ada..000000000
--- a/apps/ligo-virgo-e2e/src/integration/app.spec.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { getBoard, getTitle } from '../support/app.po';
-
-describe('ligo-virgo', () => {
- beforeEach(() => cy.visit('/'));
-
- it('basic load check', () => {
- getTitle().contains('👋 Get Started with AFFiNE');
-
- cy.get('.block_container').contains('The Essentials');
-
- getBoard().click();
- cy.get('.tl-inner-div').contains('The Essentials');
- });
-});
diff --git a/apps/ligo-virgo-e2e/src/support/app.po.ts b/apps/ligo-virgo-e2e/src/support/app.po.ts
deleted file mode 100644
index c2c78ef1f..000000000
--- a/apps/ligo-virgo-e2e/src/support/app.po.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const getTitle = () => cy.get('span[title]');
-export const getDoc = () => cy.contains('Paper');
-export const getBoard = () => cy.contains('Edgeless');
diff --git a/apps/ligo-virgo-e2e/src/support/commands.ts b/apps/ligo-virgo-e2e/src/support/commands.ts
deleted file mode 100644
index 395bb4a20..000000000
--- a/apps/ligo-virgo-e2e/src/support/commands.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-// ***********************************************
-// This example commands.js shows you how to
-// create various custom commands and overwrite
-// existing commands.
-//
-// For more comprehensive examples of custom
-// commands please read more here:
-// https://on.cypress.io/custom-commands
-// ***********************************************
-
-// eslint-disable-next-line @typescript-eslint/no-namespace
-declare namespace Cypress {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- interface Chainable {
- // login(email: string, password: string): void;
- }
-}
-//
-// -- This is a parent command --
-// Cypress.Commands.add('login', (email, password) => {
-// console.log('Custom command example: Login', email, password);
-// });
-//
-// -- This is a child command --
-// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
-//
-//
-// -- This is a dual command --
-// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
-//
-//
-// -- This will overwrite an existing command --
-// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
diff --git a/apps/ligo-virgo-e2e/src/support/index.ts b/apps/ligo-virgo-e2e/src/support/index.ts
deleted file mode 100644
index 3d469a6b6..000000000
--- a/apps/ligo-virgo-e2e/src/support/index.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// ***********************************************************
-// This example support/index.js is processed and
-// loaded automatically before your test files.
-//
-// This is a great place to put global configuration and
-// behavior that modifies Cypress.
-//
-// You can change the location of this file or turn off
-// automatically serving support files with the
-// 'supportFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/configuration
-// ***********************************************************
-
-// Import commands.js using ES2015 syntax:
-import './commands';
diff --git a/apps/ligo-virgo-e2e/tsconfig.json b/apps/ligo-virgo-e2e/tsconfig.json
deleted file mode 100644
index e20e2ba6e..000000000
--- a/apps/ligo-virgo-e2e/tsconfig.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": {
- "sourceMap": false,
- "outDir": "../../dist/out-tsc",
- "allowJs": true,
- "types": ["cypress", "node"]
- },
- "include": ["src/**/*.ts", "src/**/*.js"]
-}
diff --git a/apps/ligo-virgo/.babelrc b/apps/ligo-virgo/.babelrc
deleted file mode 100644
index f0aa1cb96..000000000
--- a/apps/ligo-virgo/.babelrc
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/apps/ligo-virgo/.browserslistrc b/apps/ligo-virgo/.browserslistrc
deleted file mode 100644
index f1d12df4f..000000000
--- a/apps/ligo-virgo/.browserslistrc
+++ /dev/null
@@ -1,16 +0,0 @@
-# This file is used by:
-# 1. autoprefixer to adjust CSS to support the below specified browsers
-# 2. babel preset-env to adjust included polyfills
-#
-# For additional information regarding the format and rule options, please see:
-# https://github.com/browserslist/browserslist#queries
-#
-# If you need to support different browsers in production, you may tweak the list below.
-
-last 1 Chrome version
-last 1 Firefox version
-last 2 Edge major versions
-last 2 Safari major version
-last 2 iOS major versions
-Firefox ESR
-not IE 9-11 # For IE 9-11 support, remove 'not'.
\ No newline at end of file
diff --git a/apps/ligo-virgo/.eslintrc.json b/apps/ligo-virgo/.eslintrc.json
deleted file mode 100644
index 1f5ec76fb..000000000
--- a/apps/ligo-virgo/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/apps/ligo-virgo/jest.config.js b/apps/ligo-virgo/jest.config.js
deleted file mode 100644
index 5b49bd23d..000000000
--- a/apps/ligo-virgo/jest.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-module.exports = {
- displayName: 'ligo-virgo',
- preset: '../../jest.preset.js',
- transform: {
- 'node_modules\\/.+\\.js$': 'jest-esm-transformer',
- '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../coverage/apps/ligo-virgo',
- transformIgnorePatterns: [],
-};
diff --git a/apps/ligo-virgo/package.json b/apps/ligo-virgo/package.json
deleted file mode 100644
index 270901439..000000000
--- a/apps/ligo-virgo/package.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "name": "ligo-virgo",
- "version": "1.0.0",
- "license": "MIT",
- "description": "",
- "main": "jest.config.js",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- "keywords": [],
- "author": "AFFiNE ",
- "dependencies": {
- "@emotion/babel-plugin": "^11.10.2",
- "@mui/icons-material": "^5.8.4"
- },
- "devDependencies": {
- "firebase": "^9.9.3",
- "mini-css-extract-plugin": "^2.6.1",
- "webpack": "^5.74.0"
- }
-}
diff --git a/apps/ligo-virgo/project.json b/apps/ligo-virgo/project.json
deleted file mode 100644
index 8412a6e16..000000000
--- a/apps/ligo-virgo/project.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "sourceRoot": "apps/ligo-virgo/src",
- "projectType": "application",
- "targets": {
- "build": {
- "executor": "@nrwl/web:webpack",
- "outputs": ["{options.outputPath}"],
- "defaultConfiguration": "production",
- "options": {
- "compiler": "babel",
- "outputPath": "dist/apps/ligo-virgo",
- "index": "apps/ligo-virgo/src/index.html",
- "baseHref": "/",
- "main": "apps/ligo-virgo/src/index.tsx",
- "polyfills": "apps/ligo-virgo/src/polyfills.ts",
- "tsConfig": "apps/ligo-virgo/tsconfig.app.json",
- "assets": ["apps/ligo-virgo/src/assets"],
- "styles": [],
- "scripts": [],
- "webpackConfig": "apps/ligo-virgo/webpack.config.js"
- },
- "configurations": {
- "production": {
- "fileReplacements": [
- {
- "replace": "apps/ligo-virgo/src/environments/environment.ts",
- "with": "apps/ligo-virgo/src/environments/environment.prod.ts"
- }
- ],
- "optimization": true,
- "outputHashing": "all",
- "sourceMap": false,
- "namedChunks": true,
- "extractLicenses": false,
- "vendorChunk": false,
- "generateIndexHtml": false
- }
- }
- },
- "serve": {
- "executor": "@nrwl/web:dev-server",
- "options": {
- "buildTarget": "ligo-virgo:build:development",
- "hmr": true,
- "proxyConfig": "apps/ligo-virgo/proxy.conf.json",
- "open": true
- },
- "configurations": {
- "production": {
- "buildTarget": "ligo-virgo:build:production",
- "hmr": false,
- "open": false
- }
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": ["apps/ligo-virgo/**/*.{ts,tsx,js,jsx}"]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/apps/ligo-virgo"],
- "options": {
- "jestConfig": "apps/ligo-virgo/jest.config.js",
- "passWithNoTests": true
- }
- },
- "check": {
- "executor": "./tools/executors/tsCheck:tsCheck"
- }
- },
- "tags": ["app:ligo-virgo"]
-}
diff --git a/apps/ligo-virgo/proxy.conf.json b/apps/ligo-virgo/proxy.conf.json
deleted file mode 100644
index a225b3f59..000000000
--- a/apps/ligo-virgo/proxy.conf.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "/collaboration": {
- "target": "http://127.0.0.1:3000/",
- "ws": true,
- "changeOrigin": true,
- "secure": false
- }
-}
diff --git a/apps/ligo-virgo/src/assets/.gitkeep b/apps/ligo-virgo/src/assets/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/apps/ligo-virgo/src/custom-formatter.ts b/apps/ligo-virgo/src/custom-formatter.ts
deleted file mode 100644
index 18bb39a22..000000000
--- a/apps/ligo-virgo/src/custom-formatter.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { AsyncBlock } from '@toeverything/framework/virgo';
-import { isDev } from '@toeverything/utils';
-
-/**
- * Ported from https://github.com/vuejs/core/blob/main/packages/runtime-core/src/customFormatter.ts
- * See [Custom Object Formatters in Chrome DevTools](https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U)
- */
-
-const isAsyncBlock = (x: unknown): x is AsyncBlock => {
- return x instanceof AsyncBlock;
-};
-
-export function initCustomFormatter() {
- if (!isDev || typeof window === 'undefined') {
- return;
- }
-
- const bannerStyle = {
- style: 'color: #eee; background: #3F6FDB; margin-right: 5px; padding: 2px; border-radius: 4px',
- };
- const typeStyle = {
- style: 'color: #eee; background: #DB6D56; margin-right: 5px; padding: 2px; border-radius: 4px',
- };
-
- // custom formatter for Chrome
- // https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html
- const formatter = {
- header(obj: unknown, config = { expand: false }) {
- if (!isAsyncBlock(obj) || config.expand) {
- return null;
- }
-
- return [
- 'div',
- {},
- ['span', bannerStyle, 'AsyncBlock'],
- ['span', typeStyle, obj.type],
- // @ts-expect-error Debug at development environment
- `${JSON.stringify(obj.raw_data.properties)}`,
- ];
- },
- hasBody(obj: unknown) {
- return true;
- },
- body(obj: unknown) {
- return ['object', { object: obj, config: { expand: true } }];
- },
- };
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- if ((window as any).devtoolsFormatters) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).devtoolsFormatters.push(formatter);
- } else {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (window as any).devtoolsFormatters = [formatter];
- }
-}
-
-initCustomFormatter();
diff --git a/apps/ligo-virgo/src/environments/environment.prod.ts b/apps/ligo-virgo/src/environments/environment.prod.ts
deleted file mode 100644
index da7c84f63..000000000
--- a/apps/ligo-virgo/src/environments/environment.prod.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const environment = {
- production: true,
-};
diff --git a/apps/ligo-virgo/src/environments/environment.ts b/apps/ligo-virgo/src/environments/environment.ts
deleted file mode 100644
index 855830c23..000000000
--- a/apps/ligo-virgo/src/environments/environment.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// This file can be replaced during build by using the `fileReplacements` array.
-// When building for production, this file is replaced with `environment.prod.ts`.
-
-export const environment = {
- production: false,
-};
diff --git a/apps/ligo-virgo/src/index.html b/apps/ligo-virgo/src/index.html
deleted file mode 100644
index 2d1ed8fb8..000000000
--- a/apps/ligo-virgo/src/index.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
- AFFiNE - All In One Workos
-
-
-
-
-
-
diff --git a/apps/ligo-virgo/src/index.tsx b/apps/ligo-virgo/src/index.tsx
deleted file mode 100644
index 19d5446ee..000000000
--- a/apps/ligo-virgo/src/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { createRoot } from 'react-dom/client';
-import { BrowserRouter } from 'react-router-dom';
-
-import { ThemeProvider } from '@toeverything/components/ui';
-import { FeatureFlagsProvider } from '@toeverything/datasource/feature-flags';
-
-import './custom-formatter';
-import { LigoVirgoRoutes } from './pages';
-import './styles.css';
-
-const container = document.getElementById('root');
-if (!container) {
- throw new Error('No root container found');
-}
-const root = createRoot(container);
-root.render(
-
-
-
-
-
-
-
-);
diff --git a/apps/ligo-virgo/src/pages/AppContainer.tsx b/apps/ligo-virgo/src/pages/AppContainer.tsx
deleted file mode 100644
index 05d7fa4ee..000000000
--- a/apps/ligo-virgo/src/pages/AppContainer.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { css, Global } from '@emotion/react';
-import { LayoutHeader, SettingsSidebar } from '@toeverything/components/layout';
-import { styled } from '@toeverything/components/ui';
-import { Outlet } from 'react-router-dom';
-
-export function LigoVirgoRootContainer() {
- return (
- <>
-
-
-
-
-
-
-
-
- >
- );
-}
-
-const StyledMainContainer = styled('div')({
- flex: 'auto',
- display: 'flex',
- overflowY: 'hidden',
-});
-
-const StyledContentContainer = styled('div')({
- flex: 'auto',
- display: 'flex',
- flexDirection: 'column',
- overflow: 'hidden',
-});
diff --git a/apps/ligo-virgo/src/pages/AppRoutes.tsx b/apps/ligo-virgo/src/pages/AppRoutes.tsx
deleted file mode 100644
index 4f1d84532..000000000
--- a/apps/ligo-virgo/src/pages/AppRoutes.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Navigate, Route, Routes } from 'react-router-dom';
-
-import { Login } from './account';
-import { LigoVirgoRootContainer } from './AppContainer';
-import { RoutePrivate } from './RoutePrivate';
-import { RoutePublicAutoLogin } from './RoutePublicAutoLogin';
-import { PageNotFound } from './status/page-not-found';
-import { WorkspaceNotFound } from './status/workspace-not-found';
-import { Tools } from './tools';
-import { UIPage } from './ui';
-import { WorkspaceContainer } from './workspace';
-
-export function LigoVirgoRoutes() {
- return (
-
- }>
- } />
- }
- />
- } />
-
-
-
- }
- />
- } />
-
-
- {/* put public routes here; header and sidebar are disabled here */}
-
- } />
-
-
-
- }
- />
- } />
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/RoutePrivate.tsx b/apps/ligo-virgo/src/pages/RoutePrivate.tsx
deleted file mode 100644
index 76c254610..000000000
--- a/apps/ligo-virgo/src/pages/RoutePrivate.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Navigate, useLocation } from 'react-router-dom';
-
-import { PageLoading } from '@toeverything/components/account';
-import { useUserAndSpaces } from '@toeverything/datasource/state';
-
-export type RoutePrivateProps = {
- children: JSX.Element;
- unauthorizedRedirectTo?: string;
-};
-
-/**
- * A routing component that cannot be accessed without logging in, and can only be accessed after logging in.
- */
-export function RoutePrivate({
- children,
- unauthorizedRedirectTo = '/login',
-}: RoutePrivateProps) {
- const { pathname } = useLocation();
-
- const { user, loading } = useUserAndSpaces();
-
- if (user == null && loading) {
- return ;
- }
-
- if (!user || !pathname.startsWith(`/${user.id}`)) {
- return (
-
- );
- }
-
- return children;
-}
diff --git a/apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx b/apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx
deleted file mode 100644
index c200455ea..000000000
--- a/apps/ligo-virgo/src/pages/RoutePublicAutoLogin.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Navigate, useLocation } from 'react-router-dom';
-
-import { PageLoading } from '@toeverything/components/account';
-import { useUserAndSpaces } from '@toeverything/datasource/state';
-
-export type RouteUnauthorizedOnlyProps = {
- children: JSX.Element;
-};
-
-/**
- * Routing components that are accessible without logging in and inaccessible after logging in will automatically jump to the specified route authorizedRedirectTo
- */
-export function RoutePublicAutoLogin({ children }: RouteUnauthorizedOnlyProps) {
- const { pathname } = useLocation();
-
- const { user, loading, currentSpaceId } = useUserAndSpaces();
-
- if (user == null && loading) {
- return ;
- }
-
- if (currentSpaceId) {
- return (
-
- );
- }
-
- return children;
-}
diff --git a/apps/ligo-virgo/src/pages/account/index.ts b/apps/ligo-virgo/src/pages/account/index.ts
deleted file mode 100644
index 42f6bc2be..000000000
--- a/apps/ligo-virgo/src/pages/account/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { Login } from '@toeverything/components/account';
-
-export { Login };
diff --git a/apps/ligo-virgo/src/pages/agenda/calendar/index.tsx b/apps/ligo-virgo/src/pages/agenda/calendar/index.tsx
deleted file mode 100644
index e7b3d6f7a..000000000
--- a/apps/ligo-virgo/src/pages/agenda/calendar/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function AgendaCalendar() {
- return AgendaCalendar ;
-}
diff --git a/apps/ligo-virgo/src/pages/agenda/container/index.tsx b/apps/ligo-virgo/src/pages/agenda/container/index.tsx
deleted file mode 100644
index 02d8378b6..000000000
--- a/apps/ligo-virgo/src/pages/agenda/container/index.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Outlet } from 'react-router-dom';
-import style9 from 'style9';
-
-import { MuiBox as Box } from '@toeverything/components/ui';
-
-const styles = style9.create({
- container: {
- display: 'flex',
- },
-});
-
-export default function AgendaRootContainer() {
- return (
-
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/agenda/home/index.tsx b/apps/ligo-virgo/src/pages/agenda/home/index.tsx
deleted file mode 100644
index 62b446f56..000000000
--- a/apps/ligo-virgo/src/pages/agenda/home/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function AgendaHome() {
- return AgendaHome ;
-}
diff --git a/apps/ligo-virgo/src/pages/agenda/index.tsx b/apps/ligo-virgo/src/pages/agenda/index.tsx
deleted file mode 100644
index 6a0f4bf01..000000000
--- a/apps/ligo-virgo/src/pages/agenda/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Route, Routes } from 'react-router-dom';
-
-import Calendar from './calendar';
-import Container from './container';
-import Home from './home';
-import Tasks from './tasks';
-import Today from './today';
-
-export default function AgendaContainer() {
- return (
-
- }>
- } />
- } />
- } />
- } />
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/agenda/tasks/index.tsx b/apps/ligo-virgo/src/pages/agenda/tasks/index.tsx
deleted file mode 100644
index b51d40197..000000000
--- a/apps/ligo-virgo/src/pages/agenda/tasks/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function AgendaTasks() {
- return AgendaTasks ;
-}
diff --git a/apps/ligo-virgo/src/pages/agenda/today/index.tsx b/apps/ligo-virgo/src/pages/agenda/today/index.tsx
deleted file mode 100644
index fa0f454e4..000000000
--- a/apps/ligo-virgo/src/pages/agenda/today/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function AgendaToday() {
- return AgendaToday ;
-}
diff --git a/apps/ligo-virgo/src/pages/index.ts b/apps/ligo-virgo/src/pages/index.ts
deleted file mode 100644
index c5bf888a8..000000000
--- a/apps/ligo-virgo/src/pages/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { LigoVirgoRoutes } from './AppRoutes';
diff --git a/apps/ligo-virgo/src/pages/status/page-not-found.tsx b/apps/ligo-virgo/src/pages/status/page-not-found.tsx
deleted file mode 100644
index 940961070..000000000
--- a/apps/ligo-virgo/src/pages/status/page-not-found.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Error } from '@toeverything/components/account';
-
-export function PageNotFound() {
- return ;
-}
-
-export default PageNotFound;
diff --git a/apps/ligo-virgo/src/pages/status/workspace-not-found.tsx b/apps/ligo-virgo/src/pages/status/workspace-not-found.tsx
deleted file mode 100644
index b2a8f7334..000000000
--- a/apps/ligo-virgo/src/pages/status/workspace-not-found.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Error } from '@toeverything/components/account';
-
-export function WorkspaceNotFound() {
- return (
-
- );
-}
-
-export default WorkspaceNotFound;
diff --git a/apps/ligo-virgo/src/pages/tools/container.tsx b/apps/ligo-virgo/src/pages/tools/container.tsx
deleted file mode 100644
index 11005e9fe..000000000
--- a/apps/ligo-virgo/src/pages/tools/container.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Outlet } from 'react-router-dom';
-
-export function Container() {
- return (
-
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/tools/icons/Icons.tsx b/apps/ligo-virgo/src/pages/tools/icons/Icons.tsx
deleted file mode 100644
index 48059b477..000000000
--- a/apps/ligo-virgo/src/pages/tools/icons/Icons.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import * as uiIcons from '@toeverything/components/icons';
-import { message, styled } from '@toeverything/components/ui';
-import { useRef, type ComponentType } from 'react';
-import { copy } from './copy';
-
-const IconBooth = ({
- name,
- Icon,
-}: {
- name: string;
- Icon: ComponentType;
-}) => {
- const on_click = () => {
- copy(`<${name} />`);
- message.success('Copied ~');
- };
- return (
-
-
- {name}
-
- );
-};
-
-const _icons = Object.entries(uiIcons).filter(([key]) => key !== 'timestamp');
-
-export const Icons = () => {
- const ref = useRef(null);
- return (
-
- Example:
-
-
- {`import { TextIcon } from '@toeverything/components/ui'`};
-
-
- {`Total: ${_icons.length}`}
- Click to copy.
- {`Last Updated: ${new Date(
- uiIcons.timestamp
- ).toLocaleString()}`}
-
-
- {_icons.map(([key, icon]) => {
- return (
- }
- />
- );
- })}
-
-
- );
-};
-
-const Container = styled('div')({
- height: '100vh',
- overflow: 'auto',
- color: '#98ACBD',
- padding: '20px',
-});
-
-const IconName = styled('div')({
- width: '100%',
- marginTop: '8px',
- wordBreak: 'break-all',
-});
-
-const IconContainer = styled('div')(({ theme }) => ({
- width: '112px',
- borderRadius: '4px',
- padding: '4px',
- cursor: 'pointer',
- textAlign: 'center',
- '--color-0': theme.affine.palette.hover,
- '--color-1': theme.affine.palette.icons,
-
- '& svg:first-of-type': {
- boxShadow: '0 0 6px #e0e6eb',
- },
-
- '&:hover': {
- backgroundColor: '#F5F7F8',
- },
-}));
-
-const IconsContainer = styled('div')({
- display: 'grid',
- gridTemplateColumns: 'repeat(auto-fill,112px)',
- justifyContent: 'justify',
- alignContent: 'start',
- columnGap: '16px',
- rowGap: '24px',
- marginTop: '24px',
-});
diff --git a/apps/ligo-virgo/src/pages/tools/icons/copy.ts b/apps/ligo-virgo/src/pages/tools/icons/copy.ts
deleted file mode 100644
index ed9e1b0b9..000000000
--- a/apps/ligo-virgo/src/pages/tools/icons/copy.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-const create_fake_element = (value: string) => {
- const fake_element = document.createElement('textarea');
- fake_element.style.position = 'fixed';
- fake_element.style.top = '0';
- fake_element.style.clipPath = "path('M0,0 L0,0')";
- fake_element.setAttribute('readonly', '');
- fake_element.value = value;
- return fake_element;
-};
-
-export const copy = (value: string) => {
- const fake_element = create_fake_element(value);
- document.body.appendChild(fake_element);
- fake_element.select();
- fake_element.setSelectionRange(0, fake_element.value.length);
- document.execCommand('copy');
- fake_element.remove();
-};
diff --git a/apps/ligo-virgo/src/pages/tools/icons/index.ts b/apps/ligo-virgo/src/pages/tools/icons/index.ts
deleted file mode 100644
index 9d2a7b7b2..000000000
--- a/apps/ligo-virgo/src/pages/tools/icons/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Icons } from './Icons';
diff --git a/apps/ligo-virgo/src/pages/tools/index.tsx b/apps/ligo-virgo/src/pages/tools/index.tsx
deleted file mode 100644
index ff32a85cf..000000000
--- a/apps/ligo-virgo/src/pages/tools/index.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Route, Routes } from 'react-router-dom';
-
-import { Container } from './container';
-import { Icons } from './icons';
-
-export function Tools() {
- return (
-
- }>
- } />
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/ui/index.tsx b/apps/ligo-virgo/src/pages/ui/index.tsx
deleted file mode 100644
index 48189656e..000000000
--- a/apps/ligo-virgo/src/pages/ui/index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export const UIPage = () => {
- return (
-
- This page is used to show ui components of AFFiNE ~
-
- );
-};
diff --git a/apps/ligo-virgo/src/pages/workspace/Container.tsx b/apps/ligo-virgo/src/pages/workspace/Container.tsx
deleted file mode 100644
index 92afc2248..000000000
--- a/apps/ligo-virgo/src/pages/workspace/Container.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Outlet } from 'react-router-dom';
-import style9 from 'style9';
-
-import { MuiBox as Box } from '@toeverything/components/ui';
-
-const styles = style9.create({
- container: {
- display: 'flex',
- overflow: 'hidden',
- },
-});
-
-export function WorkspaceRootContainer() {
- return (
-
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/workspace/Edgeless.tsx b/apps/ligo-virgo/src/pages/workspace/Edgeless.tsx
deleted file mode 100644
index 9d0b6a3aa..000000000
--- a/apps/ligo-virgo/src/pages/workspace/Edgeless.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { memo, useEffect } from 'react';
-import { useParams } from 'react-router';
-
-import { AffineBoard } from '@toeverything/components/affine-board';
-import { services } from '@toeverything/datasource/db-service';
-import { useUserAndSpaces } from '@toeverything/datasource/state';
-
-const MemoAffineBoard = memo(AffineBoard, (prev, next) => {
- return prev.rootBlockId === next.rootBlockId;
-});
-
-type EdgelessProps = {
- workspace: string;
-};
-
-export const Edgeless = (props: EdgelessProps) => {
- const { pageId } = useParams();
- const { user } = useUserAndSpaces();
-
- useEffect(() => {
- if (!user?.id || !props.workspace) return;
- const update_recent_pages = async () => {
- // TODO: deal with it temporarily
- await services.api.editorBlock.getWorkspaceDbBlock(
- props.workspace,
- {
- userId: user.id,
- }
- );
- };
- update_recent_pages();
- }, [user, props.workspace]);
-
- return ;
-};
diff --git a/apps/ligo-virgo/src/pages/workspace/Home.tsx b/apps/ligo-virgo/src/pages/workspace/Home.tsx
deleted file mode 100644
index 5034787eb..000000000
--- a/apps/ligo-virgo/src/pages/workspace/Home.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { services, TemplateFactory } from '@toeverything/datasource/db-service';
-import { useUserAndSpaces } from '@toeverything/datasource/state';
-import { useEffect } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-
-export function WorkspaceHome() {
- const navigate = useNavigate();
- const { workspaceId } = useParams();
- const { user } = useUserAndSpaces();
-
- useEffect(() => {
- const navigateToUserInitialPage = async () => {
- const [recentPages, userInitialPageId] = await Promise.all([
- services.api.userConfig.getRecentPages(workspaceId, user.id),
- services.api.userConfig.getUserInitialPage(
- workspaceId,
- user.id
- ),
- ]);
- // if recent pages if null, run initialize task
- if (recentPages.length === 0) {
- await services.api.editorBlock.copyTemplateToPage(
- workspaceId,
- userInitialPageId,
- TemplateFactory.generatePageTemplateByGroupKeys({
- name: '👋 Get Started with AFFiNE',
- groupKeys: [
- 'getStartedGroup0',
- 'getStartedGroup1',
- 'getStartedGroup2',
- ],
- })
- );
- }
- if (userInitialPageId) {
- navigate(`/${workspaceId}/${userInitialPageId}`);
- }
- };
- navigateToUserInitialPage();
- }, [navigate, user.id, workspaceId]);
-
- return null;
-}
diff --git a/apps/ligo-virgo/src/pages/workspace/WorkspaceContainer.tsx b/apps/ligo-virgo/src/pages/workspace/WorkspaceContainer.tsx
deleted file mode 100644
index a681e9880..000000000
--- a/apps/ligo-virgo/src/pages/workspace/WorkspaceContainer.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useUserAndSpaces } from '@toeverything/datasource/state';
-import { Route, Routes, useParams } from 'react-router';
-import { WorkspaceRootContainer } from './Container';
-import { Page } from './docs';
-import { Edgeless } from './Edgeless';
-import { WorkspaceHome } from './Home';
-import Pages from './pages';
-
-export function WorkspaceContainer() {
- const { workspaceId } = useParams();
- const { user, currentSpaceId } = useUserAndSpaces();
-
- return (
-
- }>
- } />
- }
- />
- }
- />
- } />
-
-
- );
-}
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx
deleted file mode 100644
index 5c42c2c79..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx
+++ /dev/null
@@ -1,218 +0,0 @@
-import { useEffect, useRef, useState, type UIEvent } from 'react';
-import { useParams } from 'react-router';
-
-import { AffineEditor } from '@toeverything/components/affine-editor';
-import { CollapsibleTitle } from '@toeverything/components/common';
-import {
- Activities,
- CalendarHeatmap,
- PageTree,
-} from '@toeverything/components/layout';
-import {
- MuiBox as Box,
- MuiCircularProgress as CircularProgress,
- styled,
-} from '@toeverything/components/ui';
-import {
- usePageClientWidth,
- useShowSpaceSidebar,
-} from '@toeverything/datasource/state';
-
-import { type BlockEditor } from '@toeverything/components/editor-core';
-import { useFlag } from '@toeverything/datasource/feature-flags';
-import { CollapsiblePageTree } from './collapsible-page-tree';
-import { Tabs } from './components/tabs';
-import { TabMap, TAB_TITLE } from './components/tabs/Tabs';
-import { TOC } from './components/toc';
-import { WorkspaceName } from './workspace-name';
-
-type PageProps = {
- workspace: string;
-};
-
-export function Page(props: PageProps) {
- const [activeTab, setActiveTab] = useState(
- TabMap.get(TAB_TITLE.PAGES).value
- );
- const { pageId } = useParams();
- const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
- useShowSpaceSidebar();
- const dailyNotesFlag = useFlag('BooleanDailyNotes', false);
- const onTabChange = v => setActiveTab(v);
-
- return (
-
-
- setSpaceSidebarVisible(true)}
- onMouseLeave={() => setSpaceSidebarVisible(false)}
- >
-
-
-
-
-
- {activeTab === TabMap.get(TAB_TITLE.PAGES).value && (
-
- {dailyNotesFlag && (
-
-
-
-
-
- )}
-
-
-
- {pageId ? : null}
-
-
-
- )}
-
- {activeTab === TabMap.get(TAB_TITLE.TOC).value && (
-
- )}
- {activeTab === TabMap.get(TAB_TITLE.GALLERY).value && (
-
- Gallery function coming soon...
-
- )}
-
-
-
-
-
- );
-}
-
-const EditorContainer = ({
- pageId,
- workspace,
-}: {
- pageId: string;
- workspace: string;
-}) => {
- const [lockScroll, setLockScroll] = useState(false);
- const [scrollContainer, setScrollContainer] = useState();
- const editorRef = useRef();
-
- const onScroll = (event: UIEvent) => {
- editorRef.current.getHooks().onRootNodeScroll(event);
- editorRef.current.scrollManager.emitScrollEvent(event);
- };
-
- const { setPageClientWidth } = usePageClientWidth();
- useEffect(() => {
- if (scrollContainer) {
- const obv = new ResizeObserver(e => {
- setPageClientWidth(e[0].contentRect.width);
- });
-
- obv.observe(scrollContainer);
- return () => obv.disconnect();
- }
- }, [setPageClientWidth, scrollContainer]);
-
- return (
- setScrollContainer(ref)}
- onScroll={onScroll}
- >
- {pageId ? (
- setLockScroll(true),
- unLockScroll: () => setLockScroll(false),
- }}
- />
- ) : (
-
-
-
- )}
-
- );
-};
-
-const StyledEditorContainer = styled('div')<{ lockScroll: boolean }>(
- ({ lockScroll }) => {
- return {
- width: '100%',
- overflowY: lockScroll ? 'hidden' : 'auto',
- flex: 'auto',
- };
- }
-);
-
-const LigoApp = styled('div')({
- width: '100vw',
- display: 'flex',
- flex: '1 1 0%',
- backgroundColor: 'white',
-});
-
-const LigoLeftContainer = styled('div')({
- flex: '0 0 auto',
- position: 'relative',
-});
-
-const WorkspaceSidebar = styled('div')(({ theme }) => ({
- position: 'absolute',
- bottom: '48px',
- top: '12px',
- zIndex: 1,
- display: 'flex',
- flexDirection: 'column',
- width: 300,
- minWidth: 300,
- borderRadius: '0px 10px 10px 0px',
- boxShadow: theme.affine.shadows.shadow2,
- backgroundColor: '#FFFFFF',
- transitionProperty: 'left',
- transitionDuration: '0.35s',
- transitionTimingFunction: 'ease',
- padding: '16px 12px',
-}));
-
-const WorkspaceSidebarContent = styled('div')({
- flex: 'auto',
- overflow: 'hidden auto',
- marginTop: '18px',
-});
-
-const StyledTextForGallery = styled('div')(({ theme }) => {
- return {
- display: 'flex',
- justifyContent: 'center',
- color: theme.affine.palette.menu,
- marginTop: theme.affine.spacing.lgSpacing,
- };
-});
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx
deleted file mode 100644
index 13f81c10f..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { AddIcon } from '@toeverything/components/icons';
-import {
- useCalendarHeatmap,
- usePageTree,
-} from '@toeverything/components/layout';
-import {
- IconButton,
- MuiBox as Box,
- MuiCollapse as Collapse,
- styled,
-} from '@toeverything/components/ui';
-import { services } from '@toeverything/datasource/db-service';
-import React, { useCallback, useState } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-
-const StyledBtn = styled('div')({
- height: '32px',
- display: 'flex',
- alignItems: 'center',
- color: '#98ACBD',
- textTransform: 'none',
- fontSize: '12px',
- fontWeight: '600',
- cursor: 'pointer',
- userSelect: 'none',
- flex: 1,
- marginLeft: '12px',
-});
-
-export type CollapsiblePageTreeProps = {
- title?: string;
- initialOpen?: boolean;
- children?: React.ReactNode;
- className?: string;
- style?: React.CSSProperties;
-};
-
-export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
- const { className, style, children, title, initialOpen = true } = props;
- const navigate = useNavigate();
- const { workspaceId, pageId } = useParams();
-
- const { handleAddPage } = usePageTree();
- const { addPageToday } = useCalendarHeatmap();
-
- const [open, setOpen] = useState(initialOpen);
-
- const create_page = useCallback(async () => {
- if (pageId) {
- const newPage = await services.api.editorBlock.create({
- workspace: workspaceId,
- type: 'page' as const,
- });
-
- await handleAddPage(newPage.id);
- addPageToday();
-
- navigate(`/${workspaceId}/${newPage.id}`);
- }
- }, [addPageToday, handleAddPage, navigate, pageId, workspaceId]);
-
- const [newPageBtnVisible, setNewPageBtnVisible] = useState(false);
-
- return (
- <>
- setNewPageBtnVisible(true)}
- onMouseLeave={() => setNewPageBtnVisible(false)}
- >
- setOpen(prev => !prev)}>
- {title}
-
-
- {newPageBtnVisible && (
-
-
-
- )}
-
- {children ? (
-
- {children}
-
- ) : null}
- >
- );
-}
-
-export default CollapsiblePageTree;
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx
deleted file mode 100644
index d4926a57e..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-export const Logo = ({ color, style, ...props }) => {
- return (
-
-
-
-
- );
-};
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/index.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/index.ts
deleted file mode 100644
index 33af50533..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Logo } from './Logo';
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx
deleted file mode 100644
index aea0c8837..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { styled } from '@toeverything/components/ui';
-import type { ValueOf } from '@toeverything/utils';
-
-const StyledTabs = styled('div')(({ theme }) => {
- return {
- width: '100%',
- height: '30px',
- marginTop: '12px',
- display: 'flex',
- fontSize: '12px',
- fontWeight: '600',
- };
-});
-
-const StyledTabTitle = styled('div')<{
- isActive?: boolean;
- isDisabled?: boolean;
-}>`
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
-
- line-height: 18px;
- padding-top: 4px;
- border-top: 2px solid #ecf1fb;
- position: relative;
- cursor: pointer;
- color: ${({ theme, isActive }) =>
- isActive ? theme.affine.palette.primary : 'rgba(62, 111, 219, 0.6)'};
-
- &::after {
- content: '';
- width: 0;
- height: 2px;
- background-color: ${({ isActive, theme }) =>
- isActive
- ? theme.affine.palette.primary
- : 'rgba(62, 111, 219, 0.6)'};
- position: absolute;
- left: 100%;
- top: -2px;
- transition: all 0.2s;
- }
-
- &.active {
- &::after {
- width: 100%;
- left: 0;
- transition-delay: 0.1s;
- }
- & ~ div::after {
- left: 0;
- }
- }
-`;
-
-export const TAB_TITLE = {
- PAGES: 'PAGES',
- GALLERY: 'GALLERY',
- TOC: 'TOC',
-} as const;
-
-export const TabMap = new Map<
- TabValue,
- { value: TabValue; disabled?: boolean }
->([
- [TAB_TITLE.PAGES, { value: TAB_TITLE.PAGES }],
- [TAB_TITLE.GALLERY, { value: TAB_TITLE.GALLERY, disabled: true }],
- [TAB_TITLE.TOC, { value: TAB_TITLE.TOC }],
-]);
-
-type TabValue = ValueOf;
-
-interface Props {
- activeTab: TabValue;
- onTabChange: (v: TabValue) => void;
-}
-
-const Tabs = (props: Props) => {
- const { activeTab, onTabChange } = props;
-
- return (
-
- {[...TabMap.entries()].map(([k, { value, disabled = false }]) => {
- const isActive = activeTab === value;
-
- return (
- onTabChange(value)}
- >
- {k}
-
- );
- })}
-
- );
-};
-
-export { Tabs };
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/index.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/index.ts
deleted file mode 100644
index 941e6baba..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Tabs } from './Tabs';
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/TOC.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/toc/TOC.tsx
deleted file mode 100644
index f22bf43fe..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/TOC.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import type { Virgo } from '@toeverything/components/editor-core';
-import { styled } from '@toeverything/components/ui';
-import { Protocol } from '@toeverything/datasource/db-service';
-import { useCurrentEditors } from '@toeverything/datasource/state';
-import {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useRef,
- useState,
-} from 'react';
-import { useParams } from 'react-router';
-import { BLOCK_TYPES } from './toc-enum';
-import {
- destroyEventList,
- getContentByAsyncBlocks,
- getPageTOC,
-} from './toc-util';
-import type { ListenerMap, TOCType } from './types';
-
-const StyledTOCItem = styled('a')<{ type?: string; isActive?: boolean }>(
- ({ type, isActive }) => {
- const common = {
- height: '32px',
- display: 'flex',
- alignItems: 'center',
- cursor: 'pointer',
- color: isActive ? '#3E6FDB' : '#4C6275',
- };
-
- if (type === BLOCK_TYPES.HEADING1) {
- return {
- ...common,
- padding: '0 12px',
- fontWeight: '600',
- fontSize: '16px',
- };
- }
-
- if (type === BLOCK_TYPES.HEADING2) {
- return {
- ...common,
- padding: '0 32px',
- fontSize: '14px',
- };
- }
-
- if (type === BLOCK_TYPES.HEADING3) {
- return {
- ...common,
- padding: '0 52px',
- fontSize: '12px',
- };
- }
-
- if (type === BLOCK_TYPES.GROUP) {
- return {
- ...common,
- margin: '6px 0px',
- height: '46px',
- padding: '6px 12px',
- fontWeight: '600',
- fontSize: '16px',
- borderTop: '0.5px solid #E0E6EB',
- borderBottom: '0.5px solid #E0E6EB',
- color: isActive ? '#3E6FDB' : '#98ACBD',
- };
- }
-
- return {};
- }
-);
-
-const StyledItem = styled('div')(props => {
- return {
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- };
-});
-
-const TOCContext = createContext(null);
-
-const TOCItem = props => {
- const { activeBlockId, onClick } = useContext(TOCContext);
- const { id, type, text } = props;
- const isActive = id === activeBlockId;
-
- return (
- onClick(id)}
- >
- {text}
-
- );
-};
-
-const renderTOCContent = tocDataSource => {
- return (
- <>
- {tocDataSource.map(tocItem => {
- if (tocItem?.length) {
- return renderTOCContent(tocItem);
- }
-
- const { id, type, text } = tocItem;
-
- return ;
- })}
- >
- );
-};
-
-export const TOC = () => {
- const { pageId } = useParams();
- const [tocDataSource, setTocDataSource] = useState([]);
- const [activeBlockId, setActiveBlockId] = useState('');
-
- /* store page/block unmount-listener */
- const listenerMapRef = useRef(new Map());
-
- const { currentEditors } = useCurrentEditors();
- const editor = currentEditors[pageId] as Virgo;
-
- const updateTocDataSource = useCallback(async () => {
- if (!editor) {
- return null;
- }
-
- const listenerMap = listenerMapRef.current;
-
- /* page listener: trigger update-notice when add new group */
- const pageAsyncBlock = (await editor.getBlockByIds([pageId]))?.[0];
- if (!listenerMap.has(pageAsyncBlock.id)) {
- listenerMap.set(
- pageAsyncBlock.id,
- pageAsyncBlock.onUpdate(updateTocDataSource)
- );
- }
-
- /* block listener: trigger update-notice when change block content */
- const { children = [] } =
- (await editor.queryByPageId(pageId))?.[0] || {};
- const asyncBlocks = (await editor.getBlockByIds(children)) || [];
- const { tocContents } = await getContentByAsyncBlocks(
- asyncBlocks,
- updateTocDataSource,
- listenerMap
- );
-
- /* toc: flat content */
- const tocDataSource = getPageTOC(asyncBlocks, tocContents);
- setTocDataSource(tocDataSource);
- }, [editor, pageId]);
-
- /* init toc and add page/block update-listener & unmount-listener */
- useEffect(() => {
- (async () => {
- await updateTocDataSource();
- })();
-
- /* remove listener when unmount component */
- return () => destroyEventList(listenerMapRef.current);
- }, [updateTocDataSource]);
-
- const onClick = async (blockId?: string) => {
- setActiveBlockId(blockId);
- const block = await editor.getBlockById(blockId);
- await editor.scrollManager.scrollIntoViewByBlockId(blockId);
-
- if (!block || block.type === Protocol.Block.Type.group) {
- // the group block has its own background
- return;
- }
- // See https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
- block.dom?.animate(
- [
- {
- backgroundColor: 'rgba(152, 172, 189, 0.1)',
- },
- {
- backgroundColor: 'rgba(152, 172, 189, 0)',
- },
- ],
- {
- delay: 500,
- duration: 700,
- easing: 'linear',
- }
- );
- };
-
- return (
-
- {renderTOCContent(tocDataSource)}
-
- );
-};
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/index.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/toc/index.ts
deleted file mode 100644
index e603af824..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { TOC } from './TOC';
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/toc-enum.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/toc/toc-enum.ts
deleted file mode 100644
index 5c9ad0c59..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/toc-enum.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum BLOCK_TYPES {
- GROUP = 'group',
- HEADING1 = 'heading1',
- HEADING2 = 'heading2',
- HEADING3 = 'heading3',
-}
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/toc-util.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/toc/toc-util.ts
deleted file mode 100644
index aa1154a94..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/toc-util.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-import { AsyncBlock } from '@toeverything/components/editor-core';
-import { BLOCK_TYPES } from './toc-enum';
-import type { ListenerMap, TOCType } from './types';
-
-/* 😞😞sorry, I don't know how to define unlimited dimensions array */
-const getContentByAsyncBlocks = async (
- asyncBlocks: AsyncBlock[] = [],
- callback: () => void,
- listenerMap: ListenerMap
-): Promise<{
- tocContents: any[];
-}> => {
- const collect = async (asyncBlocks): Promise => {
- /* maybe should recast it to tail recursion */
- return await Promise.all(
- asyncBlocks.map(async (asyncBlock: AsyncBlock) => {
- const asyncBlocks = await asyncBlock?.children();
-
- if (asyncBlocks?.length) {
- return collect(asyncBlocks);
- }
-
- /* add only once event listener for every block */
- if (!listenerMap.has(asyncBlock?.id)) {
- /* get update notice */
- const destroyHandler = asyncBlock?.onUpdate(callback);
-
- /* collect destroy handlers */
- listenerMap.set(asyncBlock?.id, destroyHandler);
- }
-
- const { id, type } = asyncBlock;
-
- switch (type) {
- case BLOCK_TYPES.GROUP:
- case BLOCK_TYPES.HEADING1:
- case BLOCK_TYPES.HEADING2:
- case BLOCK_TYPES.HEADING3: {
- const properties = await asyncBlock?.getProperties();
-
- return {
- id,
- type,
- text: properties?.text?.value?.[0]?.text || '',
- };
- }
- default:
- return null;
- }
- })
- );
- };
-
- return {
- tocContents: await collect(asyncBlocks),
- };
-};
-
-/**
- * get flat toc
- * @param asyncBlocks
- * @param tocContents
- */
-const getPageTOC = (asyncBlocks: AsyncBlock[], tocContents): TOCType[] => {
- return tocContents
- .reduce((tocGroupContent, tocContent, index) => {
- const { id, type } = asyncBlocks[index];
- const groupContent = {
- id,
- type,
- text: 'Untitled Group',
- };
-
- tocGroupContent.push(
- !tocContent.flat(Infinity).filter(Boolean).length
- ? groupContent
- : tocContent
- );
-
- return tocGroupContent;
- }, [])
- .flat(Infinity)
- .filter(Boolean);
-};
-
-/* destroy page/block update-listener */
-const destroyEventList = (listenerMap: ListenerMap) => {
- const eventListeners = listenerMap.values();
- listenerMap.clear();
-
- for (const eventListener of eventListeners) {
- eventListener?.();
- }
-};
-
-export { getPageTOC, getContentByAsyncBlocks, destroyEventList };
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/types.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/toc/types.ts
deleted file mode 100644
index 9d84eec52..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/components/toc/types.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type TOCType = {
- id: string;
- type: string;
- text: string;
-};
-
-export type ListenerMap = Map void>;
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx b/apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx
deleted file mode 100644
index 6e1b4880f..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/index.spec.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { render } from '@testing-library/react';
-
-import { Page } from './index';
-
-describe('App', () => {
- it('should render successfully', () => {
- const { baseElement } = render( );
-
- expect(baseElement).toBeTruthy();
- });
-
- it('should have a greeting as the title', () => {
- const { getByText } = render( );
-
- expect(getByText(/Welcome ligo-virgo/gi)).toBeTruthy();
- });
-});
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/index.ts b/apps/ligo-virgo/src/pages/workspace/docs/index.ts
deleted file mode 100644
index fc2e4f738..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Page } from './Page';
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/quick-find-portal.tsx b/apps/ligo-virgo/src/pages/workspace/docs/quick-find-portal.tsx
deleted file mode 100644
index cb7f85f6d..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/quick-find-portal.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { styled } from '@toeverything/components/ui';
-
-import SearchIcon from '@mui/icons-material/Search';
-
-const handle_search = () => {
- //@ts-ignore
- virgo.plugins.plugins['search'].renderSearch();
-};
-const QuickFindPortalContainer = styled('div')({
- position: 'relative',
- marginLeft: '10px',
- height: '22px',
- lineHeight: '22px',
- width: '220px',
- borderRadius: '8px',
- color: '#4c6275',
- fontSize: '14px',
- paddingLeft: '20px',
- cursor: 'pointer',
- ':hover': {
- backgroundColor: '#ccc',
- },
- '.shortcutIcon': {
- position: 'absolute',
- top: '3px',
- left: '0px',
- fontSize: '16px!important',
- },
-});
-
-function QuickFindPortal() {
- return (
-
- Quick Find
-
- );
-}
-
-export default QuickFindPortal;
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx
deleted file mode 100644
index b46eea521..000000000
--- a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import { PinIcon } from '@toeverything/components/icons';
-import { Input, styled } from '@toeverything/components/ui';
-import { services } from '@toeverything/datasource/db-service';
-import {
- useShowSpaceSidebar,
- useUserAndSpaces,
-} from '@toeverything/datasource/state';
-import {
- ChangeEvent,
- KeyboardEvent,
- useCallback,
- useEffect,
- useState,
-} from 'react';
-import { Logo } from './components/logo/Logo';
-
-const WorkspaceContainer = styled('div')({
- display: 'flex',
- flexDirection: 'column',
- paddingBottom: '12px',
- color: '#566B7D',
-});
-const LeftContainer = styled('div')({
- flex: 'auto',
- display: 'flex',
- height: '52px',
- alignItems: 'center',
- margin: '0 12px',
-});
-
-const LogoContainer = styled('div')({
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- height: 24,
- minWidth: 24,
-});
-
-const StyledPin = styled('div')({
- display: 'flex',
- justifyContent: 'end',
- alignItems: 'center',
-});
-
-const StyledWorkspace = styled('div')({
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'space-between',
- marginLeft: '12px',
- paddingLeft: '12px',
-});
-
-const StyledWorkspaceDesc = styled('div')({
- fontSize: '12px',
- color: '#98ACBD',
- height: '18px',
-
- display: 'flex',
- alignItems: 'center',
-});
-
-const WorkspaceNameContainer = styled('div')({
- display: 'flex',
- alignItems: 'center',
- flex: 'auto',
- width: '165px',
- height: '34px',
- input: {
- padding: '5px 10px',
- },
- span: {
- width: '100%',
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- },
-});
-
-const WorkspaceReNameContainer = styled('div')({
- height: '34px',
- display: 'flex',
- alignItems: 'center',
-
- input: {
- padding: '5px 10px',
- },
-});
-
-export const WorkspaceName = () => {
- const { currentSpaceId } = useUserAndSpaces();
- const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
- const [inRename, setInRename] = useState(false);
- const [workspaceName, setWorkspaceName] = useState('');
-
- const fetchWorkspaceName = useCallback(async () => {
- if (!currentSpaceId) {
- return;
- }
- const name = await services.api.userConfig.getWorkspaceName(
- currentSpaceId
- );
-
- setWorkspaceName(name);
- }, [currentSpaceId]);
-
- useEffect(() => {
- fetchWorkspaceName();
- }, [currentSpaceId]);
-
- useEffect(() => {
- let unobserve: () => void;
- const observe = async () => {
- unobserve = await services.api.userConfig.observe(
- { workspace: currentSpaceId },
- () => {
- fetchWorkspaceName();
- }
- );
- };
- observe();
-
- return () => {
- unobserve?.();
- };
- }, [currentSpaceId, fetchWorkspaceName]);
-
- const handleKeyDown = useCallback((e: KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.stopPropagation();
- e.preventDefault();
- setInRename(false);
- }
- }, []);
- const handleChange = useCallback(
- async (e: ChangeEvent) => {
- const name = e.target.value;
-
- await setWorkspaceName(name);
- await services.api.userConfig.setWorkspaceName(
- currentSpaceId,
- name
- );
- },
- [currentSpaceId]
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
- {inRename ? (
-
- setInRename(false)}
- />
-
- ) : (
-
- setInRename(true)}>
- {workspaceName || currentSpaceId}
-
-
- )}
-
-
- To shape, Not to Adapt.
-
-
-
-
- );
-};
diff --git a/apps/ligo-virgo/src/pages/workspace/index.ts b/apps/ligo-virgo/src/pages/workspace/index.ts
deleted file mode 100644
index be53c56f8..000000000
--- a/apps/ligo-virgo/src/pages/workspace/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { WorkspaceContainer } from './WorkspaceContainer';
diff --git a/apps/ligo-virgo/src/pages/workspace/labels/index.tsx b/apps/ligo-virgo/src/pages/workspace/labels/index.tsx
deleted file mode 100644
index aad88dc3e..000000000
--- a/apps/ligo-virgo/src/pages/workspace/labels/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function WorkspaceLabels() {
- return WorkspaceLabels ;
-}
diff --git a/apps/ligo-virgo/src/pages/workspace/pages/index.tsx b/apps/ligo-virgo/src/pages/workspace/pages/index.tsx
deleted file mode 100644
index b1ee12e53..000000000
--- a/apps/ligo-virgo/src/pages/workspace/pages/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function WorkspacePages() {
- return WorkspacePages ;
-}
diff --git a/apps/ligo-virgo/src/polyfills.ts b/apps/ligo-virgo/src/polyfills.ts
deleted file mode 100644
index 2adf3d05b..000000000
--- a/apps/ligo-virgo/src/polyfills.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
- *
- * See: https://github.com/zloirock/core-js#babel
- */
-import 'core-js/stable';
-import 'regenerator-runtime/runtime';
diff --git a/apps/ligo-virgo/src/styles.css b/apps/ligo-virgo/src/styles.css
deleted file mode 100644
index 0ca8248ea..000000000
--- a/apps/ligo-virgo/src/styles.css
+++ /dev/null
@@ -1,287 +0,0 @@
-/* resset.dev • v5.0.2 */
-
-/* # =================================================================
- # Global selectors
- # ================================================================= */
-
-html {
- box-sizing: border-box;
- -webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS */
- word-break: normal;
- -moz-tab-size: 4;
- tab-size: 4;
- overflow: hidden;
- font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma,
- PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif,
- Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
- /* prevent page shaking when scroll show or hide*/
- padding-left: calc(100vw - 100%);
-}
-
-*,
-::before,
-::after {
- background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements and pseudo elements */
- box-sizing: inherit;
-}
-
-::before,
-::after {
- text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */
- vertical-align: inherit;
-}
-
-* {
- padding: 0; /* Reset `padding` and `margin` of all elements */
- margin: 0;
-}
-
-/* # =================================================================
- # General elements
- # ================================================================= */
-
-hr {
- overflow: visible; /* Show the overflow in Edge and IE */
- height: 0; /* Add the correct box sizing in Firefox */
- color: inherit; /* Correct border color in Firefox. */
-}
-
-details,
-main {
- display: block; /* Render the `main` element consistently in IE. */
-}
-
-summary {
- display: list-item; /* Add the correct display in all browsers */
-}
-
-small {
- font-size: 80%; /* Set font-size to 80% in `small` elements */
-}
-
-[hidden] {
- display: none; /* Add the correct display in IE */
-}
-
-abbr[title] {
- border-bottom: none; /* Remove the bottom border in Chrome 57 */
- /* Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari */
- text-decoration: underline;
- text-decoration: underline dotted;
-}
-
-a {
- background-color: transparent; /* Remove the gray background on active links in IE 10 */
-}
-
-a:active,
-a:hover {
- outline-width: 0; /* Remove the outline when hovering in all browsers */
-}
-
-code,
-kbd,
-pre,
-samp {
- font-family: monospace, monospace; /* Specify the font family of code elements */
-}
-
-pre {
- font-size: 1em; /* Correct the odd `em` font sizing in all browsers */
-}
-
-b,
-strong {
- font-weight: bolder; /* Add the correct font weight in Chrome, Edge, and Safari */
-}
-
-/* https://gist.github.com/unruthless/413930 */
-sub,
-sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
-}
-
-sub {
- bottom: -0.25em;
-}
-
-sup {
- top: -0.5em;
-}
-
-table {
- border-color: inherit; /* Correct border color in all Chrome, Edge, and Safari. */
- text-indent: 0; /* Remove text indentation in Chrome, Edge, and Safari */
-}
-
-iframe {
- border-style: none;
-}
-
-/* # =================================================================
- # Forms
- # ================================================================= */
-
-input {
- border-radius: 0;
-}
-
-[type='number']::-webkit-inner-spin-button,
-[type='number']::-webkit-outer-spin-button {
- height: auto; /* Correct the cursor style of increment and decrement buttons in Chrome */
-}
-
-[type='search'] {
- -webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */
- outline-offset: -2px; /* Correct the outline style in Safari */
-}
-
-[type='search']::-webkit-search-decoration {
- -webkit-appearance: none; /* Remove the inner padding in Chrome and Safari on macOS */
-}
-
-textarea {
- overflow: auto; /* Internet Explorer 11+ */
- resize: vertical; /* Specify textarea resizability */
-}
-
-button,
-input,
-optgroup,
-select,
-textarea {
- font: inherit; /* Specify font inheritance of form elements */
-}
-
-optgroup {
- font-weight: bold; /* Restore the font weight unset by the previous rule */
-}
-
-button {
- overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */
-}
-
-button,
-select {
- text-transform: none; /* Firefox 40+, Internet Explorer 11- */
-}
-
-/* Apply cursor pointer to button elements */
-button,
-[type='button'],
-[type='reset'],
-[type='submit'],
-[role='button'] {
- cursor: pointer;
-}
-
-/* Remove inner padding and border in Firefox 4+ */
-button::-moz-focus-inner,
-[type='button']::-moz-focus-inner,
-[type='reset']::-moz-focus-inner,
-[type='submit']::-moz-focus-inner {
- border-style: none;
- padding: 0;
-}
-
-/* Replace focus style removed in the border reset above */
-button:-moz-focusring,
-[type='button']::-moz-focus-inner,
-[type='reset']::-moz-focus-inner,
-[type='submit']::-moz-focus-inner {
- outline: 1px dotted ButtonText;
-}
-
-button,
- html [type='button'], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */
- [type='reset'],
- [type='submit'] {
- -webkit-appearance: button; /* Correct the inability to style clickable types in iOS */
-}
-
-/* Remove the default button styling in all browsers */
-button,
-input,
-select,
-textarea {
- background-color: transparent;
- border-style: none;
-}
-
-a:focus,
-button:focus,
-input:focus,
-select:focus,
-textarea:focus {
- outline-width: 0;
-}
-
-/* Style select like a standard input */
-select {
- -moz-appearance: none; /* Firefox 36+ */
- -webkit-appearance: none; /* Chrome 41+ */
-}
-
-select::-ms-expand {
- display: none; /* Internet Explorer 11+ */
-}
-
-select::-ms-value {
- color: currentColor; /* Internet Explorer 11+ */
-}
-
-legend {
- border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */
- color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */
- display: table; /* Correct the text wrapping in Edge and IE */
- max-width: 100%; /* Correct the text wrapping in Edge and IE */
- white-space: normal; /* Correct the text wrapping in Edge and IE */
- max-width: 100%; /* Correct the text wrapping in Edge 18- and IE */
-}
-
-::-webkit-file-upload-button {
- /* Correct the inability to style clickable types in iOS and Safari */
- -webkit-appearance: button;
- color: inherit;
- font: inherit; /* Change font properties to `inherit` in Chrome and Safari */
-}
-
-/* Replace pointer cursor in disabled elements */
-[disabled] {
- cursor: default;
-}
-
-/* # =================================================================
- # Specify media element style
- # ================================================================= */
-
-img {
- border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */
-}
-
-/* Add the correct vertical alignment in Chrome, Firefox, and Opera */
-progress {
- vertical-align: baseline;
-}
-
-/* # =================================================================
- # Accessibility
- # ================================================================= */
-
-/* Specify the progress cursor of updating elements */
-[aria-busy='true'] {
- cursor: progress;
-}
-
-/* Specify the pointer cursor of trigger elements */
-[aria-controls] {
- cursor: pointer;
-}
-
-/* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */
-[aria-disabled='true'] {
- cursor: default;
-}
diff --git a/apps/ligo-virgo/src/template.html b/apps/ligo-virgo/src/template.html
deleted file mode 100644
index 56d1a9012..000000000
--- a/apps/ligo-virgo/src/template.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- <%= htmlWebpackPlugin.options.title %>
- <%= htmlWebpackPlugin.options.embedHeaderScript %>
-
-
-
-
-
diff --git a/apps/ligo-virgo/tsconfig.app.json b/apps/ligo-virgo/tsconfig.app.json
deleted file mode 100644
index 6d3f1cc6f..000000000
--- a/apps/ligo-virgo/tsconfig.app.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/apps/ligo-virgo/tsconfig.json b/apps/ligo-virgo/tsconfig.json
deleted file mode 100644
index 2105d4475..000000000
--- a/apps/ligo-virgo/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- // "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
- "noImplicitAny": false
- // "strictNullChecks": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.app.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/apps/ligo-virgo/tsconfig.spec.json b/apps/ligo-virgo/tsconfig.spec.json
deleted file mode 100644
index c37f7eb08..000000000
--- a/apps/ligo-virgo/tsconfig.spec.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ],
- "files": [
- "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../node_modules/@nrwl/react/typings/image.d.ts"
- ]
-}
diff --git a/apps/ligo-virgo/webpack.config.js b/apps/ligo-virgo/webpack.config.js
deleted file mode 100644
index a960c2a54..000000000
--- a/apps/ligo-virgo/webpack.config.js
+++ /dev/null
@@ -1,269 +0,0 @@
-const path = require('path');
-const zlib = require('zlib');
-const webpack = require('webpack');
-const getNxWebpackConfig = require('@nrwl/react/plugins/webpack');
-const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
-const CompressionPlugin = require('compression-webpack-plugin');
-const TerserPlugin = require('terser-webpack-plugin');
-const Style9Plugin = require('style9/webpack');
-
-const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER;
-const embedHeaderScript = process.env.AFFINE_EMBED_HEADER;
-
-module.exports = function (webpackConfig) {
- const config = getNxWebpackConfig(webpackConfig);
-
- const isProd = config.mode === 'production';
- const isE2E = process.env.NX_E2E;
-
- const style9 = {
- test: /\.(tsx|ts|js|mjs|jsx)$/,
- use: [
- {
- loader: Style9Plugin.loader,
- options: {
- minifyProperties: isProd,
- incrementalClassnames: isProd,
- },
- },
- ],
- };
-
- config.experiments.topLevelAwait = true;
-
- if (isProd) {
- config.module.rules.unshift(style9);
- config.entry = {
- main: [...config.entry.main, ...config.entry.polyfills],
- };
- config.devtool = false;
- config.output = {
- ...config.output,
- filename: '[name].[contenthash:8].js',
- chunkFilename: '[name].[chunkhash:8].js',
- hashDigestLength: 8,
- hashFunction: undefined,
- };
- config.optimization = {
- nodeEnv: 'production',
- minimize: true,
- minimizer: [
- new TerserPlugin({
- terserOptions: {
- ecma: 2020,
- },
- extractComments: true,
- parallel: true,
- }),
- new CssMinimizerPlugin(),
- ],
- splitChunks: {
- chunks: 'all',
- cacheGroups: {
- styles: {
- name: 'styles',
- type: 'css/mini-extract',
- chunks: 'all',
- enforce: true,
- },
- // auth: {
- // test: /[\\/]node_modules[\\/](@authing|@?firebase)/,
- // name: 'auth',
- // priority: -5,
- // chunks: 'all',
- // },
- edgeless: {
- test: /(libs\/components\/board-|[\\/]node_modules[\\/]@tldraw)/,
- name: 'edgeless',
- priority: -7,
- chunks: 'all',
- },
- paper: {
- test: /(libs\/framework\/(ligo|virgo|editor)|[\\/]node_modules[\\/](@codemirror|@lezer|slate))/,
- name: 'paper',
- priority: -8,
- chunks: 'all',
- },
- ui: {
- test: /[\\/]node_modules[\\/](@mui|@emotion|react|katex)/,
- name: 'ui',
- priority: -9,
- chunks: 'all',
- },
- vendor: {
- test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
- name: 'vendor',
- priority: -10,
- chunks: 'all',
- },
- },
- },
- };
- config.module.rules.unshift({
- test: /\.css$/i,
- use: [
- MiniCssExtractPlugin.loader,
- {
- loader: 'css-loader',
- options: {
- sourceMap: false,
- },
- },
- ],
- });
- config.module.rules.unshift({
- test: /\.scss$/i,
- use: [
- 'style-loader',
- {
- loader: 'css-loader',
- options: {
- sourceMap: false,
- },
- },
- {
- loader: 'postcss-loader',
- },
- ],
- });
- config.module.rules.splice(6);
- } else {
- config.module.rules.push(style9);
- config.output = {
- ...config.output,
- publicPath: '/',
- };
-
- const babelLoader = config.module.rules.find(
- rule =>
- typeof rule !== 'string' &&
- rule.loader?.toString().includes('babel-loader')
- );
- if (babelLoader && typeof babelLoader !== 'string') {
- babelLoader.options['plugins'] = [
- ...(babelLoader.options['plugins'] || []),
- [require.resolve('babel-plugin-open-source')],
- ];
- }
- }
-
- config.module.rules.unshift({
- test: /\.wasm$/,
- type: 'asset/resource',
- });
- config.resolve.fallback = { crypto: false, fs: false, path: false };
-
- addEmotionBabelPlugin(config);
-
- config.plugins = [
- ...config.plugins.filter(
- p => !(isProd && p instanceof MiniCssExtractPlugin)
- ),
- new webpack.DefinePlugin({
- JWT_DEV: !isProd,
- 'process.env.AFFINE_FEATURE_FLAG_TOKEN': JSON.stringify(
- process.env['AFFINE_FEATURE_FLAG_TOKEN']
- ),
- global: {},
- }),
- isProd &&
- !isE2E &&
- new HtmlWebpackPlugin({
- title: 'AFFiNE - All In One Workos',
- favicon: path.resolve(
- __dirname,
- './src/assets/images/favicon.ico'
- ), //favicon path
- template: path.resolve(__dirname, './src/template.html'),
- publicPath: '/',
- embedHeaderScript,
- }),
- new Style9Plugin(),
- isProd &&
- new MiniCssExtractPlugin({
- filename: '[name].[contenthash:8].css',
- chunkFilename: '[id].[chunkhash:8].css',
- }),
- isProd &&
- new CompressionPlugin({
- test: /\.(js|css|html|svg|ttf|woff)$/,
- algorithm: 'brotliCompress',
- filename: '[path][base].br',
- compressionOptions: {
- params: {
- [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
- },
- },
- }),
- isProd &&
- enableBundleAnalyzer &&
- new BundleAnalyzerPlugin({ analyzerMode: 'static' }),
- ].filter(Boolean);
-
- config.watchOptions = {
- // followSymlinks: false,
- // Ignore css to workaround webpack infinite recompile errors
- ignored: ['**/node_modules', '**/*.css'],
- };
-
- return config;
-};
-
-// TODO handle nx issue
-// see https://github.com/nrwl/nx/issues/8870
-// see https://github.com/nrwl/nx/issues/4520#issuecomment-787473383
-const addEmotionBabelPlugin = config => {
- const babelLoader = config.module.rules.find(
- rule =>
- typeof rule !== 'string' &&
- rule.loader?.toString().includes('babel-loader')
- );
- if (!babelLoader) {
- return;
- }
-
- babelLoader.options.plugins = [
- [
- require.resolve('@emotion/babel-plugin'),
- {
- // See https://github.com/mui/material-ui/issues/27380#issuecomment-928973157
- // See https://github.com/emotion-js/emotion/tree/main/packages/babel-plugin#importmap
- importMap: {
- '@toeverything/components/ui': {
- styled: {
- canonicalImport: ['@emotion/styled', 'default'],
- styledBaseImport: [
- '@toeverything/components/ui',
- 'styled',
- ],
- },
- },
- '@mui/material': {
- styled: {
- canonicalImport: ['@emotion/styled', 'default'],
- styledBaseImport: ['@mui/material', 'styled'],
- },
- },
- '@mui/material/styles': {
- styled: {
- canonicalImport: ['@emotion/styled', 'default'],
- styledBaseImport: [
- '@mui/material/styles',
- 'styled',
- ],
- },
- },
- },
- // sourceMap is on by default but source maps are dead code eliminated in production
- sourceMap: true,
- autoLabel: 'dev-only',
- labelFormat: '[filename]-[local]',
- cssPropOptimization: true,
- },
- ],
- ...(babelLoader.options.plugins ?? []),
- ];
-};
diff --git a/apps/venus/.babelrc b/apps/venus/.babelrc
deleted file mode 100644
index f0aa1cb96..000000000
--- a/apps/venus/.babelrc
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/apps/venus/.browserslistrc b/apps/venus/.browserslistrc
deleted file mode 100644
index f1d12df4f..000000000
--- a/apps/venus/.browserslistrc
+++ /dev/null
@@ -1,16 +0,0 @@
-# This file is used by:
-# 1. autoprefixer to adjust CSS to support the below specified browsers
-# 2. babel preset-env to adjust included polyfills
-#
-# For additional information regarding the format and rule options, please see:
-# https://github.com/browserslist/browserslist#queries
-#
-# If you need to support different browsers in production, you may tweak the list below.
-
-last 1 Chrome version
-last 1 Firefox version
-last 2 Edge major versions
-last 2 Safari major version
-last 2 iOS major versions
-Firefox ESR
-not IE 9-11 # For IE 9-11 support, remove 'not'.
\ No newline at end of file
diff --git a/apps/venus/.eslintrc.json b/apps/venus/.eslintrc.json
deleted file mode 100644
index 1f5ec76fb..000000000
--- a/apps/venus/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/apps/venus/package.json b/apps/venus/package.json
deleted file mode 100644
index 1d8a1287b..000000000
--- a/apps/venus/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "venus",
- "version": "1.0.0",
- "license": "MIT",
- "description": "",
- "scripts": {},
- "keywords": [],
- "author": "DarkSky ",
- "dependencies": {
- "@emotion/babel-plugin": "^11.10.2",
- "@emotion/react": "^11.10.0",
- "@emotion/styled": "^11.10.0",
- "@mui/joy": "^5.0.0-alpha.42",
- "i18next": "^21.9.1",
- "lozad": "^1.16.0",
- "react-i18next": "^11.18.4"
- },
- "devDependencies": {
- "@mdx-js/loader": "^2.1.3",
- "github-markdown-css": "^5.1.0",
- "image-minimizer-webpack-plugin": "^3.3.0",
- "imagemin": "^8.0.1",
- "imagemin-optipng": "^8.0.0",
- "mini-css-extract-plugin": "^2.6.1",
- "webpack": "^5.74.0"
- }
-}
diff --git a/apps/venus/project.json b/apps/venus/project.json
deleted file mode 100644
index a3a84c940..000000000
--- a/apps/venus/project.json
+++ /dev/null
@@ -1,76 +0,0 @@
-{
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "apps/venus/src",
- "projectType": "application",
- "targets": {
- "build": {
- "executor": "@nrwl/web:webpack",
- "outputs": ["{options.outputPath}"],
- "defaultConfiguration": "production",
- "options": {
- "compiler": "babel",
- "outputPath": "dist/apps/venus",
- "index": "apps/venus/src/index.html",
- "baseHref": "/",
- "main": "apps/venus/src/index.tsx",
- "polyfills": "apps/venus/src/polyfills.ts",
- "tsConfig": "apps/venus/tsconfig.app.json",
- "assets": ["apps/venus/src/assets"],
- "styles": [],
- "scripts": [],
- "webpackConfig": "apps/venus/webpack.config.js"
- },
- "configurations": {
- "production": {
- "fileReplacements": [
- {
- "replace": "apps/venus/src/environments/environment.ts",
- "with": "apps/venus/src/environments/environment.prod.ts"
- }
- ],
- "optimization": true,
- "outputHashing": "all",
- "sourceMap": false,
- "namedChunks": true,
- "extractLicenses": false,
- "vendorChunk": false,
- "generateIndexHtml": false
- }
- }
- },
- "serve": {
- "executor": "@nrwl/web:dev-server",
- "defaultConfiguration": "development",
- "options": {
- "buildTarget": "venus:build",
- "hmr": true,
- "open": true
- },
- "configurations": {
- "development": {
- "buildTarget": "venus:build:development"
- },
- "production": {
- "buildTarget": "venus:build:production",
- "hmr": false
- }
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": ["apps/venus/**/*.{ts,tsx,js,jsx}"]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/apps/venus"],
- "options": {
- "jestConfig": "apps/venus/jest.config.ts",
- "passWithNoTests": true
- }
- }
- },
- "tags": ["app:venus"]
-}
diff --git a/apps/venus/src/app/AboutUs.tsx b/apps/venus/src/app/AboutUs.tsx
deleted file mode 100644
index 193f87be0..000000000
--- a/apps/venus/src/app/AboutUs.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import { Box, Grid, Typography } from '@mui/joy';
-
-import 'github-markdown-css/github-markdown-light.css';
-
-import AboutText from './about.mdx';
-import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from './common';
-import KeepUpdate from './keeupdate.png';
-
-export const AboutUs = () => {
- return (
- <>
-
-
-
-
- To Shape, not to adapt.
-
-
-
-
-
-
- Deliver Building Blocks for Future SaaS Applications.
-
-
-
-
-
-
-
-
-
-
-
- window.open(
- 'https://github.com/toeverything/AFFiNE'
- )
- }
- />
-
-
-
- >
- );
-};
diff --git a/apps/venus/src/app/Icons.tsx b/apps/venus/src/app/Icons.tsx
deleted file mode 100644
index ec30f2067..000000000
--- a/apps/venus/src/app/Icons.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/* eslint-disable max-lines */
-/* eslint-disable @typescript-eslint/naming-convention */
-import GitHubIcon from '@mui/icons-material/GitHub';
-import { Button, SvgIcon } from '@mui/joy';
-// eslint-disable-next-line no-restricted-imports
-import { useMediaQuery } from '@mui/material';
-import { useTranslation } from 'react-i18next';
-
-export const DiscordIcon = (props: any) => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export const GitHub = (props: { center?: boolean; flat?: boolean }) => {
- const matches = useMediaQuery('(max-width: 1024px)');
- const { t } = useTranslation();
-
- return (
- {
- window.open('https://github.com/toeverything/AFFiNE');
- }}
- {...(props.flat ? { variant: 'plain' } : {})}
- {...{
- sx: {
- margin: 'auto 1em',
- fontSize: '24px',
- '@media (max-width: 1024px)': {
- fontSize: '16px',
- },
- ...(props.flat
- ? {
- padding: matches ? '0' : '0 0.5em',
- margin: 'auto 0',
- ':hover': { backgroundColor: 'unset' },
- }
- : {}),
- ...(props.center
- ? {
- padding: '0.5em 1em',
- fontSize: '2em',
- ':hover': {
- backgroundColor: '#0c60d9',
- boxShadow: '2px 2px 20px #08f4',
- },
- }
- : {}),
- },
- }}
- startIcon={ }
- size="lg"
- >
- {props.center ? t('Check GitHub') : t('GitHub')}
-
- );
-};
diff --git a/apps/venus/src/app/IndexPage/Alternatives.tsx b/apps/venus/src/app/IndexPage/Alternatives.tsx
deleted file mode 100644
index 4c7645a9d..000000000
--- a/apps/venus/src/app/IndexPage/Alternatives.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { Box, Typography } from '@mui/joy';
-import { styled } from '@mui/joy/styles';
-import clsx from 'clsx';
-import { useEffect, useMemo, useState } from 'react';
-// eslint-disable-next-line no-restricted-imports
-import { useMediaQuery } from '@mui/material';
-
-const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
- position: 'relative',
- width: '24em',
- height: '128px',
- transform: 'translateY(-8px)',
- overflowY: 'hidden',
- '@media (max-width: 1024px)': {
- width,
- height: '48px',
- transform: 'translateY(0)',
- },
- '& .scroll-element': {
- width: 'inherit',
- height: 'inherit',
- position: 'absolute',
- left: '0',
- top: '-23px',
- paddingTop: '22px',
- lineHeight: '96px',
- '@media (max-width: 1024px)': {
- lineHeight: '32px',
- },
- },
- '& .scroll-element.active': {
- animation: 'primary 500ms linear infinite',
- },
- '.primary.active': {
- animation: 'primary 500ms linear infinite',
- },
- '.secondary.active': {
- animation: 'secondary 500ms linear infinite',
- },
- '@keyframes primary': {
- from: {
- top: '0%',
- },
- to: {
- top: '-100%',
- },
- },
- '@keyframes secondary': {
- from: {
- top: '100%',
- },
- to: {
- top: '0%',
- },
- },
-}));
-
-const _alternatives = ['Notion', 'Miro', 'Monday'];
-const _alternativesSize = [8, 6, 10];
-
-export const AlternativesProduct = () => {
- const [idx, setIdx] = useState(0);
- const [last, current] = useMemo(
- () => [
- _alternatives[idx],
- _alternatives[idx + 1] ? _alternatives[idx + 1] : _alternatives[0],
- ],
- [idx]
- );
- const maxWidth = useMemo(() => _alternativesSize[idx], [idx]);
- const [active, setActive] = useState(false);
- const matches = useMediaQuery('(max-width: 1024px)');
-
- useEffect(() => {
- const handle = setInterval(() => {
- setActive(true);
- setTimeout(
- () => {
- setIdx(idx => (_alternatives[idx + 1] ? idx + 1 : 0));
- setActive(false);
- },
- matches ? 450 : 380
- );
- }, 2000);
- return () => clearInterval(handle);
- }, [matches]);
-
- return (
-
-
-
- {last}
-
-
-
-
- {current}
-
-
-
- );
-};
diff --git a/apps/venus/src/app/IndexPage/FunctionTabs.tsx b/apps/venus/src/app/IndexPage/FunctionTabs.tsx
deleted file mode 100644
index 7a87df1dd..000000000
--- a/apps/venus/src/app/IndexPage/FunctionTabs.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-import { Box, Grid, Typography } from '@mui/joy';
-import { useState } from 'react';
-// eslint-disable-next-line no-restricted-imports
-import { Paper, Slide, Tab, Tabs, useMediaQuery } from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import { AFFiNEImage } from '../common';
-import ShapeImage from './shape.png';
-import TaskImage from './task.png';
-
-export const FunctionTabs = () => {
- const matches = useMediaQuery('(max-width: 1024px)');
- const { t } = useTranslation();
- const [tab, selectTab] = useState(0);
- return (
-
- selectTab(value)}
- centered
- >
-
-
-
-
-
-
-
-
- {t('description2.part1')}
-
-
- {t('description2.part2')}
-
-
- {t('description2.part3')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('description3.part1')}
-
-
- {t('description3.part2')}
-
-
- {t('description3.part3')}
-
-
- {t('description3.part4')}
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/apps/venus/src/app/IndexPage/IndexPage.tsx b/apps/venus/src/app/IndexPage/IndexPage.tsx
deleted file mode 100644
index e664fa834..000000000
--- a/apps/venus/src/app/IndexPage/IndexPage.tsx
+++ /dev/null
@@ -1,292 +0,0 @@
-/* eslint-disable max-lines */
-/* eslint-disable @typescript-eslint/naming-convention */
-import { useTranslation } from 'react-i18next';
-
-import { Box, Button, Grid, Typography } from '@mui/joy';
-import { LogoIcon } from '@toeverything/components/icons';
-// eslint-disable-next-line no-restricted-imports
-import { useMediaQuery } from '@mui/material';
-
-import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from '../common';
-import { GitHub } from '../Icons';
-import { AlternativesProduct } from './Alternatives';
-import CollaborationImage from './collaboration.png';
-import { FunctionTabs } from './FunctionTabs';
-import PageImage from './page.png';
-
-const AFFiNEOnline = (props: { center?: boolean; flat?: boolean }) => {
- const matches = useMediaQuery('(max-width: 1024px)');
- const { t } = useTranslation();
- return (
- {
- window.open('https://livedemo.affine.pro/');
- }}
- {...(props.flat ? { variant: 'plain' } : {})}
- {...{
- sx: {
- margin: 'auto 1em',
- fontSize: '24px',
- '@media (max-width: 1024px)': {
- fontSize: '16px',
- },
- ...(props.flat
- ? {
- padding: matches ? '0' : '0 0.5em',
- ':hover': { backgroundColor: 'unset' },
- }
- : {}),
- ...(props.center
- ? {
- padding: '0.5em 1em',
- fontSize: '2em',
- backgroundColor: '#000',
- ':hover': {
- backgroundColor: '#0c60d9',
- boxShadow: '2px 2px 20px #08f4',
- },
- }
- : {}),
- },
- }}
- startIcon={ }
- size="lg"
- >
- {t('Try it Online')}
-
- );
-};
-
-export function IndexPage() {
- const matches = useMediaQuery('(max-width: 1024px)');
- const { t } = useTranslation();
-
- return (
- <>
-
-
-
-
- {t('Open Source')},
-
-
- {t('Privacy First')}
-
-
-
-
-
-
-
- {t('Alternative')}
-
-
-
-
-
-
- {t('description1.part1')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {t('description1.part2')}
-
-
-
-
-
-
- {t('description1.part3')}
-
-
- {t('description1.part4')}
-
-
-
-
-
-
-
- {t('description4.part1')}
-
-
- {t('description4.part2')}
-
-
- {t('description4.part3')}
-
-
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/apps/venus/src/app/IndexPage/collaboration.png b/apps/venus/src/app/IndexPage/collaboration.png
deleted file mode 100644
index bad2daedd..000000000
Binary files a/apps/venus/src/app/IndexPage/collaboration.png and /dev/null differ
diff --git a/apps/venus/src/app/IndexPage/index.ts b/apps/venus/src/app/IndexPage/index.ts
deleted file mode 100644
index b36af7308..000000000
--- a/apps/venus/src/app/IndexPage/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { IndexPage } from './IndexPage';
diff --git a/apps/venus/src/app/IndexPage/page.png b/apps/venus/src/app/IndexPage/page.png
deleted file mode 100644
index d5f393262..000000000
Binary files a/apps/venus/src/app/IndexPage/page.png and /dev/null differ
diff --git a/apps/venus/src/app/IndexPage/shape.png b/apps/venus/src/app/IndexPage/shape.png
deleted file mode 100644
index ad0ea8088..000000000
Binary files a/apps/venus/src/app/IndexPage/shape.png and /dev/null differ
diff --git a/apps/venus/src/app/IndexPage/task.png b/apps/venus/src/app/IndexPage/task.png
deleted file mode 100644
index 279d968cb..000000000
Binary files a/apps/venus/src/app/IndexPage/task.png and /dev/null differ
diff --git a/apps/venus/src/app/MdxMarks.tsx b/apps/venus/src/app/MdxMarks.tsx
deleted file mode 100644
index 1e3890cae..000000000
--- a/apps/venus/src/app/MdxMarks.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Box, Typography } from '@mui/joy';
-
-type NameProps = {
- name: string;
- link: string;
- title: string;
- description?: string;
-};
-
-export const Name = (props: NameProps) => {
- return (
- <>
-
-
-
- {props.name}
-
-
-
- {' | '}
- {props.title}
-
-
- {props.description ? (
-
- {props.description}
-
- ) : null}
- >
- );
-};
-
-export const Padding = () => {
- return
;
-};
diff --git a/apps/venus/src/app/about.mdx b/apps/venus/src/app/about.mdx
deleted file mode 100644
index d05c28500..000000000
--- a/apps/venus/src/app/about.mdx
+++ /dev/null
@@ -1,88 +0,0 @@
-import { Name, Padding } from './MdxMarks';
-
-
-
-## Do Contact US if you
-
-- Want to know more about AFFiNE as a collaborative knowledge base;
-- Want to join us;
-- Want to build your own block-based applications.
-
-General contact: [contact@toeverything.info](mailto:contact@toeverything.info)
-
-Send Resume to: [hr@toeverything.info](mailto:hr@toeverything.info)
-
-
-
-## Team Member
-
-### Founder & Co-founders
-
-
-
-
-
-
-
-
-
-### Architectural Developers
-
-
-
-
-
-
-
-
-
-## The Philosophy of AFFiNE
-
-People need better building blocks for future applications. And it should not be so hard to develop collaborative, transferable, smart spreadsheets or block editors.
-
-Timothy Berners-Lee once taught us about the idea of the semantic web, where all the data can be interpreted in any form while the "truth" is kept. This gives our best image of an ideal knowledge base by far, that sorting of information, planning of project and goals as well as creating of knowledge can be all together.
-
-We have witnessed waves of paradigm shift so many times. At first, everything was noted on office-like apps or DSL like LaTeX, then we found todo-list apps and WYSIWYG markdown editors better for writing and planning.
-
-Finally, here comes Notion and Miro, who take advantage of the idea of blocks to further liberate our creativity. It is all perfect... without waste operations and redundant information. And, we insist that privacy first should always be given by default. That's why we are making AFFiNE.
diff --git a/apps/venus/src/app/common/Footer.tsx b/apps/venus/src/app/common/Footer.tsx
deleted file mode 100644
index f44ad6fda..000000000
--- a/apps/venus/src/app/common/Footer.tsx
+++ /dev/null
@@ -1,402 +0,0 @@
-/* eslint-disable max-lines */
-/* eslint-disable @typescript-eslint/naming-convention */
-import { useTranslation } from 'react-i18next';
-
-import { Box, Button, Grid, styled, Typography } from '@mui/joy';
-
-import GitHubIcon from '@mui/icons-material/GitHub';
-import RedditIcon from '@mui/icons-material/Reddit';
-import TelegramIcon from '@mui/icons-material/Telegram';
-import TwitterIcon from '@mui/icons-material/Twitter';
-
-import { LogoIcon } from '@toeverything/components/icons';
-import { DiscordIcon, GitHub } from '../Icons';
-// import LogoImage from '../logo.png';
-// import { AFFiNEImage } from './index';
-
-export const AFFiNEFooter = ({
- keepupdate = true,
-}: {
- keepupdate?: boolean;
-}) => {
- const { t } = useTranslation();
-
- return (
- <>
- {keepupdate ? (
- <>
-
-
-
- {/* */}
-
-
-
-
-
- {t('BuildFor')}
-
-
-
-
-
-
-
-
- {t('KeepUpdated')}
-
-
-
-
-
- >
- ) : null}
-
-
-
- {t('Join')}
-
-
-
-
-
-
- window.open(
- 'https://github.com/toeverything/AFFiNE/'
- )
- }
- >
-
-
-
-
-
-
- GitHub
-
-
-
-
-
-
- window.open('https://www.reddit.com/r/Affine/')
- }
- >
-
-
-
-
-
-
- Reddit
-
-
-
-
-
-
- window.open('https://twitter.com/AffineOfficial')
- }
- >
-
-
-
-
-
-
- Twitter
-
-
-
-
-
- window.open('https://t.me/affineworkos')}
- >
-
-
-
-
-
-
- Telegram
-
-
-
-
-
-
- window.open('https://discord.gg/yz6tGVsf5p')
- }
- >
-
-
-
-
-
-
- Discord
-
-
-
-
-
-
-
-
- AFFiNE is an
-
- #OpenSource
-
- software, built with
-
- BlockSuite
-
-
-
-
-
-
-
- Copyright © 2022 Toeverything
-
-
-
- >
- );
-};
-
-const StyledLink = styled('a')({
- fontWeight: '900',
- color: '#000',
- textDecoration: 'none',
-
- '&:hover': {
- textDecoration: 'underline',
- },
-});
diff --git a/apps/venus/src/app/common/Header.tsx b/apps/venus/src/app/common/Header.tsx
deleted file mode 100644
index 5de98049a..000000000
--- a/apps/venus/src/app/common/Header.tsx
+++ /dev/null
@@ -1,179 +0,0 @@
-/* eslint-disable @typescript-eslint/naming-convention */
-import { useTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-
-import { Button, Grid } from '@mui/joy';
-import Option from '@mui/joy/Option';
-import Select from '@mui/joy/Select';
-// eslint-disable-next-line no-restricted-imports
-import GitHubIcon from '@mui/icons-material/GitHub';
-// eslint-disable-next-line no-restricted-imports
-import { styled, useMediaQuery } from '@mui/material';
-import AFFiNETextLogo from './affine-text-logo.png';
-import { HoverMenu } from './HoverMenu';
-import { MobileHeader } from './MobileHeader';
-
-import { LOCALES } from '../i18n';
-
-export const AFFiNEHeader = () => {
- const matches = useMediaQuery('(max-width: 1024px)');
- const navigate = useNavigate();
- const { i18n, t } = useTranslation();
-
- const changeLanguage = (event: any) => {
- i18n.changeLanguage(event);
- };
- const matchesIPAD = useMediaQuery('(max-width: 1080px)');
- if (matchesIPAD) {
- return ;
- }
- return (
-
-
- navigate('/')}
- />
- navigate('/aboutus')}
- variant="plain"
- color="neutral"
- sx={{
- padding: matches ? '0' : '0 0.5em',
- fontSize: '16px',
- }}
- size="md"
- >
- {t('AboutUs')}
-
- window.open('https://blog.affine.pro')}
- variant="plain"
- color="neutral"
- sx={{
- padding: matches ? '0' : '0 0.5em',
- fontSize: '16px',
- }}
- size="md"
- >
- {t('Blog')}
-
- window.open('https://docs.affine.pro/')}
- variant="plain"
- color="neutral"
- sx={{
- padding: matches ? '0' : '0 0.5em',
- fontSize: '16px',
- }}
- size="md"
- >
- {t('Docs')}
-
- window.open('https://feedback.affine.pro/')}
- variant="plain"
- color="neutral"
- sx={{
- padding: matches ? '0' : '0 0.5em',
- fontSize: '16px',
- }}
- size="md"
- >
- {t('Feedback')}
-
-
-
-
- window.open('https://github.com/toeverything/AFFiNE')
- }
- sx={{
- padding: matches ? '0' : '0 0.5em',
- fontSize: '16px',
- }}
- size="md"
- >
-
-
- window.open('https://livedemo.affine.pro/')}
- variant="plain"
- color="neutral"
- sx={{
- padding: matches ? '0' : '0 0.5em',
- fontSize: '16px',
- }}
- size="md"
- >
- {t('Try it Online')}
-
- {
- window.open(href);
- }}
- />
-
- {LOCALES.map(lang => (
-
- {lang.originalName}
-
- ))}
-
-
-
- );
-};
-
-const Container = styled(Grid)({
- position: 'fixed',
- top: 0,
- left: '50%',
- transform: 'translateX(-50%)',
- width: '100%',
- paddingTop: '1em',
- paddingBottom: '1em',
- backgroundColor: '#fff',
- zIndex: 1500,
- maxWidth: '1440px',
- margin: 'auto',
- marginTop: '0 !important',
-});
-
-const StyledImage = styled('img')({
- height: '24px',
- marginRight: '16px',
- cursor: 'pointer',
-});
diff --git a/apps/venus/src/app/common/HoverMenu.tsx b/apps/venus/src/app/common/HoverMenu.tsx
deleted file mode 100644
index d7bcded3e..000000000
--- a/apps/venus/src/app/common/HoverMenu.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import Button from '@mui/joy/Button';
-// eslint-disable-next-line no-restricted-imports
-import { styled, Tooltip, type TooltipProps } from '@mui/material';
-
-interface HoverMenuProps {
- title: string;
- options: Array<{ title: string; value: string }>;
- onSelect: (value: string) => void;
-}
-
-export function HoverMenu({ title, options, onSelect }: HoverMenuProps) {
- return (
-
- {options.map(option => {
- return (
- {
- onSelect(option.value);
- }}
- >
- {option.title}
-
- );
- })}
- >
- }
- >
-
- {title}
-
-
- );
-}
-
-const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
-
-))(({ theme }) => ({
- '& .MuiTooltip-tooltip': {
- backgroundColor: 'white',
- boxShadow: theme.shadows[4],
- color: '#272930',
- zIndex: '1500',
- },
-}));
-
-const ListItem = styled('a')({
- display: 'block',
- fontSize: '16px',
- lineHeight: '32px',
- padding: '5px',
- cursor: 'pointer',
- borderRadius: '4px',
- color: '#272930',
- textDecoration: 'none',
-
- '&:hover': {
- color: '#131418',
- backgroundColor: '#eeeff0',
- },
-});
diff --git a/apps/venus/src/app/common/MobileHeader.tsx b/apps/venus/src/app/common/MobileHeader.tsx
deleted file mode 100644
index a856e177e..000000000
--- a/apps/venus/src/app/common/MobileHeader.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import Close from '@mui/icons-material/Close';
-import ExpandLess from '@mui/icons-material/ExpandLess';
-import ExpandMore from '@mui/icons-material/ExpandMore';
-import GitHubIcon from '@mui/icons-material/GitHub';
-import Menu from '@mui/icons-material/Menu';
-import { Button, Grid } from '@mui/joy';
-import { useRef, useState } from 'react';
-// eslint-disable-next-line no-restricted-imports
-import {
- Checkbox,
- Collapse,
- Drawer,
- List,
- ListItemButton,
- ListItemText,
- styled,
-} from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-import { LOCALES } from '../i18n';
-import AFFiNETextLogo from './affine-text-logo.png';
-
-export const MobileHeader = () => {
- const navigate = useNavigate();
- const { i18n, t } = useTranslation();
- const anchor = useRef(null);
- const [openedDrawer, setDrawerState] = useState(false);
- const handleDrawerOpenState = () => {
- setDrawerState(!openedDrawer);
- };
- const [contactUsCollapse, setContactUsCollapse] = useState(false);
- const handleContactUsClick = () => {
- setContactUsCollapse(!contactUsCollapse);
- };
-
- const [languageCollapse, setLanguageCollapse] = useState(false);
- const handleLanguageClick = () => {
- setLanguageCollapse(!languageCollapse);
- };
-
- return (
-
-
- navigate('/')}
- />
-
-
- window.open(
- 'https://github.com/toeverything/AFFiNE'
- )
- }
- >
-
-
-
- {openedDrawer ? : }
-
-
-
- setDrawerState(false)}
- >
-
-
- navigate('/aboutus')}>
-
-
-
- window.open('https://blog.affine.pro')
- }
- >
-
-
-
- window.open('https://docs.affine.pro/')
- }
- >
-
-
-
- window.open('https://feedback.affine.pro/')
- }
- >
-
-
-
-
- {contactUsCollapse ? (
-
- ) : (
-
- )}
-
-
-
- window.open('https://discord.gg/Arn7TqJBvG')
- }
- >
-
-
-
- window.open('https://t.me/affineworkos')
- }
- >
-
-
-
- window.open(
- 'https://www.reddit.com/r/Affine/'
- )
- }
- >
-
-
-
- window.open(
- 'https://medium.com/@affineworkos'
- )
- }
- >
-
-
-
- window.open(
- 'mailto:contact@toeverything.info'
- )
- }
- >
-
-
-
-
-
- {languageCollapse ? : }
-
-
- {LOCALES.map(lang => (
-
- i18n.changeLanguage(lang.tag)
- }
- >
-
-
-
- ))}
-
-
-
-
-
- );
-};
-
-const Container = styled(Grid)({
- position: 'fixed',
- top: 0,
- left: '50%',
- transform: 'translateX(-50%)',
- width: '100%',
- paddingTop: '1em',
- backgroundColor: '#fff',
- zIndex: 1500,
- maxWidth: '1440px',
- margin: 'auto',
- marginTop: '0 !important',
-});
-
-const StyledImage = styled('img')({
- height: '24px',
- marginRight: '16px',
- cursor: 'pointer',
-});
-
-const StyledDrawerContainer = styled('div')({
- paddingTop: '72px',
-});
diff --git a/apps/venus/src/app/common/index.tsx b/apps/venus/src/app/common/index.tsx
deleted file mode 100644
index ea2457a83..000000000
--- a/apps/venus/src/app/common/index.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable @typescript-eslint/naming-convention */
-import { styled } from '@mui/joy/styles';
-
-export const AFFiNEImage = styled('img')({
- maxWidth: '100%',
- objectFit: 'contain',
-});
-
-export { AFFiNEFooter } from './Footer';
-export { AFFiNEHeader } from './Header';
diff --git a/apps/venus/src/app/i18n/index.ts b/apps/venus/src/app/i18n/index.ts
deleted file mode 100644
index f4428a9b0..000000000
--- a/apps/venus/src/app/i18n/index.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-import i18next, { Resource } from 'i18next';
-import {
- I18nextProvider,
- initReactI18next,
- useTranslation,
-} from 'react-i18next';
-import { LOCALES } from './resources';
-import type en_US from './resources/en.json';
-
-// See https://react.i18next.com/latest/typescript
-declare module 'react-i18next' {
- interface CustomTypeOptions {
- // custom namespace type if you changed it
- // defaultNS: 'ns1';
- // custom resources type
- resources: {
- en: typeof en_US;
- };
- }
-}
-
-const STORAGE_KEY = 'i18n_lng';
-
-export { i18n, useTranslation, I18nProvider, LOCALES };
-
-const resources = LOCALES.reduce(
- (acc, { tag, res }) => ({ ...acc, [tag]: { translation: res } }),
- {}
-);
-
-const fallbackLng = LOCALES[0].tag;
-const standardizeLocale = (language: string) => {
- if (LOCALES.find(locale => locale.tag === language)) return language;
- if (
- LOCALES.find(
- locale => locale.tag === language.slice(0, 2).toLowerCase()
- )
- )
- return language;
- return fallbackLng;
-};
-
-const language = standardizeLocale(
- localStorage.getItem(STORAGE_KEY) ?? navigator.language
-);
-
-const i18n = i18next.createInstance();
-i18n.use(initReactI18next).init({
- lng: language,
- fallbackLng,
- debug: process.env['NODE_ENV'] === 'development',
-
- resources,
- interpolation: {
- escapeValue: false, // not needed for react as it escapes by default
- },
-});
-
-i18n.on('languageChanged', lng => {
- localStorage.setItem(STORAGE_KEY, lng);
-});
-
-const I18nProvider = I18nextProvider;
diff --git a/apps/venus/src/app/i18n/resources/de.json b/apps/venus/src/app/i18n/resources/de.json
deleted file mode 100644
index 2a3c133e4..000000000
--- a/apps/venus/src/app/i18n/resources/de.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "Blog": "Blog",
- "AboutUs": "Über uns",
- "Open Source": "Open Source",
- "Docs": "Dokumentation",
- "Feedback": "Feedback",
- "ContactUs": "Kontaktiere uns",
- "Privacy First": "Privatsphären-respektierend",
- "Alternative": "Alternative",
- "Check GitHub": "Weitere Informationen befinden sich auf Github",
- "GitHub": "GitHub",
- "Try it Online": "Teste es online",
- "language": "Sprache",
- "description1": {
- "part1": "Affine ist die gemeinschaftliche Wissensdatenbank der nächsten Generation für Profis.",
- "part2": "Es ist nicht nur eine Sammlung von Dokumenten, Whiteboards und Tabellen.",
- "part3": "Ändere jeden Block, wie du ihn gerne hättest.",
- "part4": "Verabschiede dich von Redundanz. Speichere deine Daten einmalig und behalte sie wie du sie gerne hättest."
- },
- "description2": {
- "part1": "Gestalte deine Seite",
- "part2": "Dokumente, Kanbans und Datenbanken sind voll funktional - immer und überall. Eine wahrhaftige \"Was du siehst, ist das, was du bekommst.\"-Umgebung,",
- "part3": "Alle Seiten können wahlweise in einer Dokumenten- (Paper Mode) und einer Whiteboard-Ansicht (Edge less Mode) angezeigt werden."
- },
- "description3": {
- "part1": "Plane deine Aufgabe",
- "part2": "Kein Chaos mehr bei der Verwaltung mehrerer Ansichten.",
- "part3": "Erstelle eine TODO Listenelement und bearbeite es nahtlos in einem Kanban.",
- "part4": "Die Verwaltung von mehrdimensionalen Tabellen sollte einfach sein - und ist es jetzt auch."
- },
- "description4": {
- "part1": "Der Respekt deiner Privatsphäre und Zusammenarbeit stehen an erster Stelle. Keine Kompromisse.",
- "part2": "Wir lassen uns nicht gerne einschränken, und das sollten Sie auch nicht. Der Schutz der Privatsphäre ist die Grundlage für alles, was wir tun, aber sie sollte uns nicht aufhalten - deshalb gibt es keine Kompromisse.",
- "part3": "Ihre Daten gehören Ihnen; sie sind immer lokal gespeichert und gesichert und stehen Ihnen jederzeit zur Verfügung. Gleichzeitig können Sie die Funktionen für die Zusammenarbeit nutzen, z. B. die Bearbeitung in Echtzeit und die gemeinsame Nutzung mit anderen, ohne dass eine Cloud eingerichtet werden muss."
- },
- "BuildFor": "Build for an open and semantic future",
- "KeepUpdated": "Keep Updated on",
- "Join": "Join Our Community"
-}
diff --git a/apps/venus/src/app/i18n/resources/en.json b/apps/venus/src/app/i18n/resources/en.json
deleted file mode 100644
index 4dfc81af8..000000000
--- a/apps/venus/src/app/i18n/resources/en.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "Blog": "Blog",
- "AboutUs": "About Us",
- "Open Source": "Open Source",
- "Docs": "Docs",
- "Feedback": "Feedback",
- "ContactUs": "Contact Us",
- "Privacy First": "Privacy First",
- "Alternative": "Alternative",
- "Check GitHub": "Check GitHub",
- "GitHub": "GitHub",
- "Try it Online": "Try it Online",
- "language": "Language",
- "description1": {
- "part1": "Affine is the next-generation collaborative knowledge base for professionals.",
- "part2": "It's not just a collection of Docs, whiteboard, and tables.",
- "part3": "Transform any building block as you like.",
- "part4": "Say goodbye to redundancy. Store your data once, and keep your data as you like it."
- },
- "description2": {
- "part1": "Shape Your Page",
- "part2": "Docs, Kanbans, and Databases are all fully functional anywhere, anytime. A truly what-you-see-is-what-you-get environment for your data.",
- "part3": "All pages come with a document (Paper Mode) and whiteboard (Edgeless Mode) view."
- },
- "description3": {
- "part1": "Plan Your Task",
- "part2": "No more chaos managing multiple views.",
- "part3": "Set a TODO with Markdown, and seamlessly edit it within a Kanban.",
- "part4": "Managing multi-dimensional tables should be this simple - and now it is."
- },
- "description4": {
- "part1": "Privacy-first, and collaborative. No compromises whatsoever.",
- "part2": "We don't like being locked-in, and neither should you. Privacy is at the foundation of everything we do, but it should not limit us that's why there are no compromises.",
- "part3": "Your data is yours;it is always locally stored and secured - available to you always. While still being able to enjoy collaboration features such as real-time editing and sharing with others, without any cloud setup."
- },
- "BuildFor": "Build for an open and semantic future",
- "KeepUpdated": "Keep Updated on",
- "Join": "Join Our Community"
-}
diff --git a/apps/venus/src/app/i18n/resources/es.json b/apps/venus/src/app/i18n/resources/es.json
deleted file mode 100644
index 29aefcd80..000000000
--- a/apps/venus/src/app/i18n/resources/es.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "Blog": "Blog",
- "AboutUs": "Acerca de nosotros.",
- "Open Source": "Código abierto",
- "Docs": "Documentos",
- "Feedback": "Comentarios",
- "ContactUs": "Contáctenos",
- "Alternative": "Alternativa",
- "BuildFor": "Construir por un futuro abierto y semántico",
- "Check GitHub": "Revisar en GitHub",
- "Join": "Únete a nuestra comunidad",
- "KeepUpdated": "Mantente actualizado",
- "Open Source": "Código abierto",
- "Privacy First": "La privacidad es lo primero",
- "Try it Online": "Prueba Online",
- "language": "Idioma",
- "description1": {
- "part1": "Affine es un base de conocimiento colaborativa de próxima generación para los profesionales.",
- "part2": "Es más que una simple colección de Documentos, Pizarra y Tablas.",
- "part3": "Transforma cualquier bloque como tu quieras. ",
- "part4": "Dígale adios a la redundancia, Almacene sus datos una vez, y manténgalos como usted quiera."
- },
- "description2": {
- "part1": "Dele forma a sus páginas.",
- "part2": "Los Documentos, Kanbans y Base de datos son completamente funcionales en cualquier lugar, en cualquier momento. Un verdadero entorno de lo que ves y lo que obtienes para tus datos.",
- "part3": "Todas las páginas tiene una vista de documento y de pizarra. "
- },
- "description3": {
- "part1": "Programe sus tareas.",
- "part2": "No más caos manejando múltiples vistas.",
- "part3": "Establezca un TODO con Markdown, y edítelo sin problemas en un Kanban. ",
- "part4": "La gestión de tablas multidimensionales debería ser así de fácil y ahora lo es."
- },
- "description4": {
- "part1": "Privacidad en primer lugar, y colaboración sin compromiso. ",
- "part2": "\nNo nos gusta estar encerrados, y a usted tampoco debería. La privacidad está en la base de todo lo que hacemos, pero no debe limitarnos, por eso no hay compromisos.",
- "part3": "Sus datos son suyos; siempre están almacenados y protegidos de forma local, siempre disponibles para usted. Sin dejar de lado el poder disfrutar de funciones de colaboración como la edición en tiempo real y el uso compartido con otros, sin ninguna configuración en la nube."
- }
-}
diff --git a/apps/venus/src/app/i18n/resources/index.ts b/apps/venus/src/app/i18n/resources/index.ts
deleted file mode 100644
index 7673a1d33..000000000
--- a/apps/venus/src/app/i18n/resources/index.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import de from './de.json';
-import en from './en.json';
-import es from './es.json';
-import sr from './sr.json';
-import zh_Hant from './zh-Hant.json';
-import zh_Hans from './zh.json';
-
-export const LOCALES = [
- {
- name: 'English',
- tag: 'en',
- originalName: 'English',
- res: en,
- },
- {
- name: 'Simplified Chinese',
- tag: 'zh-Hans',
- originalName: '简体中文',
- res: zh_Hans,
- },
- {
- name: 'Traditional Chinese',
- tag: 'zh-Hant',
- originalName: '繁體中文',
- res: zh_Hant,
- },
- {
- name: 'Spanish',
- tag: 'es',
- originalName: 'español',
- res: es,
- },
- {
- name: 'Serbian',
- tag: 'sr',
- originalName: 'српски',
- res: sr,
- },
- {
- name: 'German',
- tag: 'de',
- originalName: 'Deutsch',
- res: de,
- },
-] as const;
diff --git a/apps/venus/src/app/i18n/resources/sr.json b/apps/venus/src/app/i18n/resources/sr.json
deleted file mode 100644
index ff853bbc4..000000000
--- a/apps/venus/src/app/i18n/resources/sr.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "Blog": "Blog",
- "AboutUs": "O nama",
- "Open Source": "Otvoreni kod",
- "Docs": "Dokumenti",
- "Feedback": "Povratne informacije",
- "ContactUs": "Kontaktirajte nas",
- "Privacy First": "Privatnost na prvom mestu",
- "Alternative": "Alternativa",
- "Check GitHub": "Proveri GitHub",
- "GitHub": "GitHub",
- "Try it Online": "Probaj onlajn",
- "language": "Jezik",
- "description1": {
- "part1": "Affine je baza znanja sledeće generacije za profesionalce koja omogućava timski rad.",
- "part2": "Nije samo zbirka dokumenata, panela i tabela.",
- "part3": "Promeni svaki elemenat po svojoj želji.",
- "part4": "Bez nepotrebnosti. Sačuvaj svoje podatke jednom, i čuvaj ih onako kako ti to želiš."
- },
- "description2": {
- "part1": "Oblikuj svoju stranicu",
- "part2": "Dokumenti, Kanban-table i baze podataka su potpuno funkcionalne bilo kad i bilo gde. Prava ovo-što-vidiš-je-ono-što-dobijaš sredina za tvoje podatke.",
- "part3": "Sve stranice dolaze sa dokument-izgledom (Papirni način rada) i panel-izgledom (Bezokvirni način rada)."
- },
- "description3": {
- "part1": "Planiraj svoje zadatke",
- "part2": "Nema više haosa nastalog pri upravljanju višestrukim izgledima.",
- "part3": "Kreiraj TODO listu u Markdown i jednostavno je prilagođavaj u Kanban prikazu.",
- "part4": "Upravljanje multidimenzionalnim tabelama bi trebalo biti jednostavno, a sada i jeste."
- },
- "description4": {
- "part1": "Privatnost i timski rad na prvom mestu. Bez kompromisa bez obzira na sve.",
- "part2": "Ne volimo biti zatvoreni, a ne bi trebali to ni Vi. Privatnost je osnova svega što radimo, ali nas ona ne bi smela ni ograničavati i zato nema kompromisa.",
- "part3": "Vaši podaci su vaši; čuvaju se lokalno na Vašem računaru i uvek su Vam dostupni. Pritom ipak možete uživati u prednostima saradnje kao što su uređivanje i deljenje dokumenata u stvarnom vremenu s drugima, bez ikakvog postavljanja onlajn oblaka."
- },
- "BuildFor": "Stvoreno za otvorenu i semantičku budućnost",
- "KeepUpdated": "Ažuriraj",
- "Join": "Pridruži se zajednici"
-}
diff --git a/apps/venus/src/app/i18n/resources/zh-Hant.json b/apps/venus/src/app/i18n/resources/zh-Hant.json
deleted file mode 100644
index 879e75556..000000000
--- a/apps/venus/src/app/i18n/resources/zh-Hant.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
- "Blog": "博客",
- "AboutUs": "關於我們",
- "Open Source": "開源",
- "Docs": "文檔",
- "Feedback": "反饋",
- "ContactUs": "聯繫我們",
- "Privacy First": "隱私第一",
- "Alternative": "的另一種選擇",
- "Check GitHub": "在 Github 中查看",
- "GitHub": "GitHub",
- "Try it Online": "在線嘗試",
- "language": "語言",
- "description1": {
- "part1": "Affine 是面向專業用戶的下一代協同知識庫",
- "part2": "Affine 不僅僅集合着文檔、白板和表格。",
- "part3": "隨心所欲切換構塊模式。",
- "part4": "告別冗余,存儲數據一次就好,放在任何您想要的位置。"
- },
- "description2": {
- "part1": "塑形您的頁面",
- "part2": "隨時隨地,文檔、看板、數據庫都完全可用。真正“所見即所得”的數據處理環境。",
- "part3": "所有頁面都配備文檔(紙張模式)和白板(無邊緣模式)視圖。"
- },
- "description3": {
- "part1": "計畫您的任務",
- "part2": "多視圖管理不再混亂。",
- "part3": "既可使用 Markdown 設置 TODO,又可無縫在看板視圖中編輯。",
- "part4": "管理多維表格本當易如反掌——攜手Affine,确实易如反掌"
- },
- "description4": {
- "part1": "隱私第一,合作無間,毫不妥協。",
- "part2": "我們不喜歡被軟件綁架,您也是。我們的任何努力雖建立在尊重用戶隱私基礎上,也游刃有餘。這解釋了為什麼我們毫不妥協。",
- "part3": "您創造的數據屬於您,安全永久存儲在本地供您隨時調用。同時您也能盡享協同的便利,無需雲設置就能實時編輯、共享給他人。"
- },
- "BuildFor": "構建一個開放和語義化的未來",
- "KeepUpdated": "持續更新在",
- "Join": "加入我們的社群"
-}
diff --git a/apps/venus/src/app/i18n/resources/zh.json b/apps/venus/src/app/i18n/resources/zh.json
deleted file mode 100644
index 18f44bf88..000000000
--- a/apps/venus/src/app/i18n/resources/zh.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "Blog": "博客",
- "AboutUs": "关于我们",
- "Open Source": "开源",
- "Privacy First": "隐私第一",
- "Docs": "文档",
- "Feedback": "反馈",
- "ContactUs": "联系我们",
- "Alternative": "的另一种选择",
- "Check GitHub": "GitHub 中查看",
- "Try it Online": "在线试用",
- "language": "语言",
- "description1": {
- "part1": "Affine 是面向专业人士的下一代协同知识库",
- "part2": "它不仅仅是一个文档、白板和表格的集合。",
- "part3": "可以根据需要转换任何构建块。",
- "part4": "向冗余说再见吧。将数据存储一次,并保留您喜欢的数据。"
- },
- "description2": {
- "part1": "塑造您的页面",
- "part2": "文档、看板和数据库在任何地方、任何时候都是完全可用的,一个真正的“所见即所得”的数据环境。",
- "part3": "所有页面都有一个文档(纸张模式)和白板(无边缘模式)视图。"
- },
- "description3": {
- "part1": "计划您的任务",
- "part2": "不再混乱地管理多个视图。",
- "part3": "使用 Markdown 设置 TODO,并在看板中无缝地编辑它。",
- "part4": "管理多维表格本应该就这么简单——现在的它就是这么简单。"
- },
- "description4": {
- "part1": "隐私第一,合作无间,绝不妥协。",
- "part2": "我们不喜欢被关起来,您也不应该。隐私是我们做任何事情的基础,但它不应该限制我们,这就是为什么我们决不妥协。",
- "part3": "您的数据是您自己的,它总是安全的在本地存储并随时供您使用。您不需要任何云设置就可以享受协作功能,即时编辑和与他人共享。"
- },
- "BuildFor": "构建一个开放和语义化的未来",
- "KeepUpdated": "持续更新在",
- "Join": "加入我们的社区"
-}
diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx
deleted file mode 100644
index 6064cef41..000000000
--- a/apps/venus/src/app/index.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { Outlet, Route, Routes } from 'react-router-dom';
-
-import { Container } from '@mui/joy';
-import { CssVarsProvider } from '@mui/joy/styles';
-
-import { AboutUs } from './AboutUs';
-import { IndexPage } from './IndexPage';
-
-const VenusContainer = () => {
- return (
-
- div': {
- marginTop: '1em',
- },
- }}
- >
-
-
-
- );
-};
-
-export function VenusRoutes() {
- return (
-
- }>
- } />
- } />
-
-
- );
-}
diff --git a/apps/venus/src/app/keeupdate.png b/apps/venus/src/app/keeupdate.png
deleted file mode 100644
index 7bdc36986..000000000
Binary files a/apps/venus/src/app/keeupdate.png and /dev/null differ
diff --git a/apps/venus/src/app/logo.png b/apps/venus/src/app/logo.png
deleted file mode 100644
index 54262b7cb..000000000
Binary files a/apps/venus/src/app/logo.png and /dev/null differ
diff --git a/apps/venus/src/assets/.gitkeep b/apps/venus/src/assets/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/apps/venus/src/assets/images/favicon.ico b/apps/venus/src/assets/images/favicon.ico
deleted file mode 100644
index b972ff61c..000000000
Binary files a/apps/venus/src/assets/images/favicon.ico and /dev/null differ
diff --git a/apps/venus/src/assets/images/og.png b/apps/venus/src/assets/images/og.png
deleted file mode 100644
index ad0ea8088..000000000
Binary files a/apps/venus/src/assets/images/og.png and /dev/null differ
diff --git a/apps/venus/src/environments/environment.prod.ts b/apps/venus/src/environments/environment.prod.ts
deleted file mode 100644
index da7c84f63..000000000
--- a/apps/venus/src/environments/environment.prod.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export const environment = {
- production: true,
-};
diff --git a/apps/venus/src/environments/environment.ts b/apps/venus/src/environments/environment.ts
deleted file mode 100644
index 855830c23..000000000
--- a/apps/venus/src/environments/environment.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// This file can be replaced during build by using the `fileReplacements` array.
-// When building for production, this file is replaced with `environment.prod.ts`.
-
-export const environment = {
- production: false,
-};
diff --git a/apps/venus/src/index.html b/apps/venus/src/index.html
deleted file mode 100644
index b139b035e..000000000
--- a/apps/venus/src/index.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
- Venus
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/venus/src/index.tsx b/apps/venus/src/index.tsx
deleted file mode 100644
index 2bd7c320c..000000000
--- a/apps/venus/src/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { StrictMode } from 'react';
-import { createRoot } from 'react-dom/client';
-import { BrowserRouter } from 'react-router-dom';
-import './app/i18n';
-
-import { VenusRoutes } from './app';
-
-const container = document.getElementById('root');
-if (!container) {
- throw new Error('No root container found');
-}
-const root = createRoot(container);
-root.render(
-
-
-
-
-
-);
diff --git a/apps/venus/src/mdx.d.ts b/apps/venus/src/mdx.d.ts
deleted file mode 100644
index e6f1511e1..000000000
--- a/apps/venus/src/mdx.d.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-declare module '*.mdx' {
- let MDXComponent: (props: any) => JSX.Element;
- export default MDXComponent;
-}
diff --git a/apps/venus/src/polyfills.ts b/apps/venus/src/polyfills.ts
deleted file mode 100644
index 2adf3d05b..000000000
--- a/apps/venus/src/polyfills.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`.
- *
- * See: https://github.com/zloirock/core-js#babel
- */
-import 'core-js/stable';
-import 'regenerator-runtime/runtime';
diff --git a/apps/venus/src/template.html b/apps/venus/src/template.html
deleted file mode 100644
index d0c4a84e0..000000000
--- a/apps/venus/src/template.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
- <%= htmlWebpackPlugin.options.title %>
-
-
-
-
-
diff --git a/apps/venus/tsconfig.app.json b/apps/venus/tsconfig.app.json
deleted file mode 100644
index a4f9eff7d..000000000
--- a/apps/venus/tsconfig.app.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "jest.config.ts",
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.d.ts"]
-}
diff --git a/apps/venus/tsconfig.json b/apps/venus/tsconfig.json
deleted file mode 100644
index 4aa485608..000000000
--- a/apps/venus/tsconfig.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "extends": "../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true,
- "strictNullChecks": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.app.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/apps/venus/tsconfig.spec.json b/apps/venus/tsconfig.spec.json
deleted file mode 100644
index f60e49f7b..000000000
--- a/apps/venus/tsconfig.spec.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "jest.config.ts",
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ],
- "files": [
- "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../node_modules/@nrwl/react/typings/image.d.ts"
- ]
-}
diff --git a/apps/venus/webpack.config.js b/apps/venus/webpack.config.js
deleted file mode 100644
index 57c78f9f3..000000000
--- a/apps/venus/webpack.config.js
+++ /dev/null
@@ -1,219 +0,0 @@
-const path = require('path');
-const zlib = require('zlib');
-const webpack = require('webpack');
-const getNxWebpackConfig = require('@nrwl/react/plugins/webpack');
-const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
-const CompressionPlugin = require('compression-webpack-plugin');
-const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
-const TerserPlugin = require('terser-webpack-plugin');
-
-const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER;
-
-module.exports = function (webpackConfig) {
- const config = getNxWebpackConfig(webpackConfig);
-
- const isProd = config.mode === 'production';
-
- const mdx = {
- test: /\.mdx?$/,
- use: [
- {
- loader: '@mdx-js/loader',
- /** @type {import('@mdx-js/loader').Options} */
- options: {},
- },
- ],
- };
-
- config.experiments.topLevelAwait = true;
-
- if (isProd) {
- config.entry = {
- main: [...config.entry.main, ...config.entry.polyfills],
- };
- config.devtool = false;
- config.output = {
- ...config.output,
- filename: '[name].[contenthash:8].js',
- chunkFilename: '[name].[chunkhash:8].js',
- hashDigestLength: 8,
- hashFunction: undefined,
- };
- config.optimization = {
- nodeEnv: 'production',
- minimize: true,
- minimizer: [
- new TerserPlugin({
- terserOptions: {
- ecma: 2020,
- },
- extractComments: true,
- parallel: true,
- }),
- new CssMinimizerPlugin(),
- new ImageMinimizerPlugin({
- minimizer: {
- implementation: ImageMinimizerPlugin.imageminMinify,
- options: {
- plugins: [['optipng', { optimizationLevel: 5 }]],
- },
- },
- }),
- ],
- splitChunks: {
- chunks: 'all',
- cacheGroups: {
- styles: {
- name: 'styles',
- type: 'css/mini-extract',
- chunks: 'all',
- enforce: true,
- },
- ui: {
- test: /[\\/]node_modules[\\/](@mui|@emotion|react|katex)/,
- name: 'ui',
- priority: -9,
- chunks: 'all',
- },
- vendor: {
- test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
- name: 'vendor',
- priority: -10,
- chunks: 'all',
- },
- },
- },
- };
- config.module.rules.unshift({
- test: /\.css$/i,
- use: [
- MiniCssExtractPlugin.loader,
- {
- loader: 'css-loader',
- options: {
- sourceMap: false,
- },
- },
- ],
- });
- config.module.rules.splice(4);
- } else {
- config.output = {
- ...config.output,
- publicPath: '/',
- };
-
- const babelLoader = config.module.rules.find(
- rule =>
- typeof rule !== 'string' &&
- rule.loader?.toString().includes('babel-loader')
- );
- if (babelLoader && typeof babelLoader !== 'string') {
- babelLoader.options['plugins'] = [
- ...(babelLoader.options['plugins'] || []),
- [require.resolve('babel-plugin-open-source')],
- ];
- }
- }
-
- config.module.rules.push(mdx);
-
- addEmotionBabelPlugin(config);
-
- config.plugins = [
- ...config.plugins.filter(
- p => !(isProd && p instanceof MiniCssExtractPlugin)
- ),
- new webpack.DefinePlugin({
- JWT_DEV: !isProd,
- global: {},
- }),
- isProd &&
- new HtmlWebpackPlugin({
- title: 'AFFiNE - All In One Workos',
- favicon: path.resolve(
- __dirname,
- './src/assets/images/favicon.ico'
- ), //favicon path
- template: path.resolve(__dirname, './src/template.html'),
- publicPath: '/',
- }),
- isProd &&
- new MiniCssExtractPlugin({
- filename: '[name].[contenthash:8].css',
- chunkFilename: '[id].[chunkhash:8].css',
- }),
- isProd &&
- new CompressionPlugin({
- test: /\.(js|css|html|svg|ttf|woff)$/,
- algorithm: 'brotliCompress',
- filename: '[path][base].br',
- compressionOptions: {
- params: {
- [zlib.constants.BROTLI_PARAM_QUALITY]: 11,
- },
- },
- }),
- isProd &&
- enableBundleAnalyzer &&
- new BundleAnalyzerPlugin({ analyzerMode: 'static' }),
- ].filter(Boolean);
-
- // Workaround for webpack infinite recompile errors
- config.watchOptions = {
- // followSymlinks: false,
- ignored: ['**/*.css'],
- };
-
- return config;
-};
-
-// TODO handle nx issue
-// see https://github.com/nrwl/nx/issues/8870
-// see https://github.com/nrwl/nx/issues/4520#issuecomment-787473383
-const addEmotionBabelPlugin = config => {
- const babelLoader = config.module.rules.find(
- rule =>
- typeof rule !== 'string' &&
- rule.loader?.toString().includes('babel-loader')
- );
- if (!babelLoader) {
- return;
- }
-
- babelLoader.options.plugins = [
- [
- require.resolve('@emotion/babel-plugin'),
- {
- // See https://github.com/mui/material-ui/issues/27380#issuecomment-928973157
- // See https://github.com/emotion-js/emotion/tree/main/packages/babel-plugin#importmap
- importMap: {
- '@mui/material': {
- styled: {
- canonicalImport: ['@emotion/styled', 'default'],
- styledBaseImport: ['@mui/material', 'styled'],
- },
- },
- '@mui/material/styles': {
- styled: {
- canonicalImport: ['@emotion/styled', 'default'],
- styledBaseImport: [
- '@mui/material/styles',
- 'styled',
- ],
- },
- },
- },
- // sourceMap is on by default but source maps are dead code eliminated in production
- sourceMap: true,
- autoLabel: 'dev-only',
- labelFormat: '[filename]-[local]',
- cssPropOptimization: true,
- },
- ],
- ...(babelLoader.options.plugins ?? []),
- ];
-};
diff --git a/babel.config.json b/babel.config.json
deleted file mode 100644
index af8aa6b9d..000000000
--- a/babel.config.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "babelrcRoots": ["*"]
-}
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 543f38068..c278df5d3 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -10,12 +10,17 @@ Use the table of contents icon on the top left corner of this document to get to
## New contributor guide
+Currently we have two versions of AFFiNE:
+
+- [AFFiNE Pre-Alpha](https://livedemo.affine.pro/). This version users the branch `master`, however is no longer actively developed and will be archived in the future.
+- [AFFiNE Alpha](https://pathfinder.affine.pro/). This version uses the 'pathfinder' branch, this is the latest version under active development. We plan to update this to the master branch in the near future.
+
To get an overview of the project, read the [README](../README.md). Here are some resources to help you get started with open source contributions:
-- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
-- [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
-- [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
-- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
+- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
+- [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
+- [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
+- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
## Getting started
@@ -47,15 +52,15 @@ For more information about using a codespace for working on GitHub documentation
2. Fork the repository.
-- Using GitHub Desktop:
+- Using GitHub Desktop:
- - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
- - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
+ - [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
+ - Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
-- Using the command line:
- - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
+- Using the command line:
+ - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
-3. Install or update to **Node.js v16**. For more information, see [the development guide](contributing/development.md).
+3. Install or update to **Node.js v16**.
4. Create a working branch and start with your changes!
@@ -71,13 +76,13 @@ Once your changes are ready, don't forget to self-review to speed up the review
When you're finished with the changes, create a pull request, also known as a PR.
-- Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request.
-- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
-- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
- Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
-- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
-- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
-- If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues.
+- Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request.
+- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
+- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
+ Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
+- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
+- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
+- If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues.
### Your PR is merged!
diff --git a/docs/jobs/affine.pro-intern.md b/docs/jobs/affine.pro-intern.md
new file mode 100644
index 000000000..efae7efa3
--- /dev/null
+++ b/docs/jobs/affine.pro-intern.md
@@ -0,0 +1,31 @@
+## What we do
+
+We **AFFiNE** hold a vision of shaping a world semantically connected through block components in modern applications.
+We're open for Fullstack Engineer positions across the BlockSuite sub-team. The **BlockSuite** team works on creating the best **block-editor** and **open-block** protocol for use in AFFiNE. Paving the way for a new generation of SaaS software and developers.
+
+## Fullstack Engineer Intern
+
+### This position is for:
+
+- Developing AFFiNE **the open source way**, including coding and community engagement.
+- Improving our **block editor** and **graphics editor**.
+- Researching on better activation of potential subscribers.
+
+### What we are looking for:
+
+- Software engineering experience with **editor** or **graphics** and professional real-world use cases.
+- Experience and proficiency in **TypeScript** and a **second programming language** preferably **Rust**.
+- Strong communication and writing skills in English.
+- Ability to work in a diverse and cross-functional team with skill and ease.
+- A love for open source, sharing our visions and working under those values.
+
+### It would be great if you are:
+
+- Heavy user of knowledge/project management tools.
+- Enthusiastic about AFFiNE products as a user or contributor.
+
+### Contact us:
+
+Interested? Send us your CV to [contact@toeverything.info](mailto:contact@toeverything.info).
+
+Feel free to include any extra information (GitHub link, previous projects, personal blog etc.).
diff --git a/docs/jobs/summary.md b/docs/jobs/summary.md
index 903f7071e..387d665fa 100644
--- a/docs/jobs/summary.md
+++ b/docs/jobs/summary.md
@@ -1,8 +1,16 @@
# Jobs
-- [Fullstack Engineer ](./affine.pro.md) @[affine.pro](http://affine.pro/)
+- [Fullstack Engineer - Global ](./affine.pro-remote.md) @[affine.pro](http://affine.pro/)
- Rust ·TS · Virgo · JWST · Remote , Global
+ Rust ·TS · BlockSuite · OctoBase(JWST) · Remote , Global
+
+- [Fullstack Engineer - China ](./affine.pro.md) @[affine.pro](http://affine.pro/)
+
+ Rust ·TS · BlockSuite · OctoBase(JWST) · Remote , China
+
+- [Fullstack Engineer Intern ](./affine.pro-intern.md) @[affine.pro](http://affine.pro/)
+
+ Rust ·TS · BlockSuite · OctoBase(JWST) · Hangzhou , China
- [Full Stack Platform Engineer ](./mysc.app.md) @[mysc.app](https://mysc.app/)
diff --git a/jest.config.js b/jest.config.js
index 5d3b9ff97..4423f5ad0 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,5 +1,19 @@
-const { getJestProjects } = require('@nrwl/jest');
-
+/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
- projects: getJestProjects(),
+ projects: ['/packages/app'],
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ moduleNameMapper: {
+ '^(\\.{1,2}/.*)\\.js$': '$1',
+ },
+ transform: {
+ // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
+ // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
+ '^.+\\.tsx?$': [
+ 'ts-jest',
+ {
+ useESM: true,
+ },
+ ],
+ },
};
diff --git a/jest.preset.js b/jest.preset.js
deleted file mode 100644
index c93ed9dc7..000000000
--- a/jest.preset.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const nxPreset = require('@nrwl/jest/preset');
-
-module.exports = { ...nxPreset };
diff --git a/libs/.gitkeep b/libs/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/libs/components/account/.babelrc b/libs/components/account/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/account/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/account/.eslintrc.json b/libs/components/account/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/account/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/account/README.md b/libs/components/account/README.md
deleted file mode 100644
index ca22ad566..000000000
--- a/libs/components/account/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-account
-
-- components about user accounts
-
-## Running unit tests
-
-Run `nx test components-account` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/account/jest.config.js b/libs/components/account/jest.config.js
deleted file mode 100644
index 1007e552a..000000000
--- a/libs/components/account/jest.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = {
- displayName: 'components-account',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/account',
-};
diff --git a/libs/components/account/package.json b/libs/components/account/package.json
deleted file mode 100644
index 00634cf9b..000000000
--- a/libs/components/account/package.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "name": "@toeverything/components/account",
- "version": "0.0.1",
- "license": "MIT",
- "dependencies": {
- "@emotion/styled": "^11.9.3"
- },
- "devDependencies": {}
-}
diff --git a/libs/components/account/project.json b/libs/components/account/project.json
deleted file mode 100644
index 4913d6649..000000000
--- a/libs/components/account/project.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "sourceRoot": "libs/components/account/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/account",
- "tsConfig": "libs/components/account/tsconfig.lib.json",
- "project": "libs/components/account/package.json",
- "entryFile": "libs/components/account/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/account/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/account/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/account"],
- "options": {
- "jestConfig": "libs/components/account/jest.config.js",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/account/src/error.tsx b/libs/components/account/src/error.tsx
deleted file mode 100644
index 55489187d..000000000
--- a/libs/components/account/src/error.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-import style9 from 'style9';
-
-import { keyframes, MuiBox as Box } from '@toeverything/components/ui';
-
-const styles = style9.create({
- container: {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '100%',
- height: 'calc( 100vh - 64px )',
- },
-});
-
-type ErrorProps = {
- title?: string;
- subTitle?: string;
- action1Text?: string;
- action1Route?: string;
- action2Text?: string;
- action2Route?: string;
- clearOnClick?: boolean;
-};
-
-const floatAnimation = keyframes`
-100% { transform: translateY(20px) }
-`;
-
-/**
- * Exception related pages
- */
-export function Error({
- title = 'Page Not Found',
- subTitle,
- action1Text,
- action1Route,
- action2Text,
- action2Route,
- clearOnClick = false,
-}: ErrorProps) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
{title}
- {subTitle ?
{subTitle} : null}
-
-
- {/* {action1Text ? (
- {
- if (clearOnClick) {
- event.preventDefault();
- LOGOUT_LOCAL_STORAGE.forEach(name =>
- localStorage.removeItem(name)
- );
- document.cookie =
- LOGOUT_COOKIES.map(
- name =>
- name +
- '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
- ).join(' ');
-
- signOut(getAuth());
-
- window.location.href =
- action1Route || '/login';
- }
- }}
- >
- {action1Text}
-
- ) : null} */}
- {/* {action2Text ? (
-
- {action2Text}
-
- ) : null} */}
-
-
-
-
-
- );
-}
diff --git a/libs/components/account/src/index.ts b/libs/components/account/src/index.ts
deleted file mode 100644
index 8610c4e1b..000000000
--- a/libs/components/account/src/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export { Error } from './error';
-export { PageLoading } from './loading';
-export { Login } from './login';
diff --git a/libs/components/account/src/loading.tsx b/libs/components/account/src/loading.tsx
deleted file mode 100644
index b7bcb4ad4..000000000
--- a/libs/components/account/src/loading.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { MuiCircularProgress as CircularProgress } from '@toeverything/components/ui';
-import style9 from 'style9';
-
-const styles = style9.create({
- container: {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '100%',
- height: 'calc( 100vh - 64px )',
- },
-});
-
-/**
- * Loading components occupy the entire page
- */
-export function PageLoading() {
- return (
-
-
-
- );
-}
diff --git a/libs/components/account/src/login/FileSystem.tsx b/libs/components/account/src/login/FileSystem.tsx
deleted file mode 100644
index 8b70bc51e..000000000
--- a/libs/components/account/src/login/FileSystem.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { useMemo } from 'react';
-
-import { LogoIcon } from '@toeverything/components/icons';
-import { MuiButton } from '@toeverything/components/ui';
-import { services } from '@toeverything/datasource/db-service';
-import { useLocalTrigger } from '@toeverything/datasource/state';
-
-const cleanupWorkspace = (workspace: string) =>
- new Promise((resolve, reject) => {
- const req = indexedDB.deleteDatabase(workspace);
- req.addEventListener('error', e => reject(e));
- req.addEventListener('blocked', e => reject(e));
- req.addEventListener('upgradeneeded', e => reject(e));
- req.addEventListener('success', e => resolve(e));
- });
-
-const requestPermission = async (workspace: string) => {
- await cleanupWorkspace(workspace);
- // @ts-ignore
- const dirHandler = await window.showDirectoryPicker({
- id: 'AFFiNE_' + workspace,
- mode: 'readwrite',
- startIn: 'documents',
- });
- const fileHandle = await dirHandler.getFileHandle('affine.db', {
- create: true,
- });
- const file = await fileHandle.getFile();
- const initialData = new Uint8Array(await file.arrayBuffer());
-
- const exporter = async (contents: Uint8Array) => {
- try {
- const writable = await fileHandle.createWritable();
- await writable.write(contents);
- await writable.close();
- } catch (e) {
- console.log(e);
- }
- };
-
- await services.api.editorBlock.setupDataExporter(
- workspace,
- new Uint8Array(initialData),
- exporter
- );
-};
-
-export const FileSystem = (props: { onError: () => void }) => {
- const [, onSelected] = useLocalTrigger();
-
- const apiSupported = useMemo(() => {
- try {
- return 'showOpenFilePicker' in window;
- } catch (e) {
- return false;
- }
- }, []);
-
- return (
- }
- onClick={async () => {
- try {
- if (apiSupported) {
- await requestPermission('AFFiNE');
- onSelected();
- } else {
- onSelected();
- }
- } catch (e) {
- props.onError();
- }
- }}
- >
- {apiSupported ? 'Sync to Disk' : 'Try Live Demo'}
-
- );
-};
diff --git a/libs/components/account/src/login/Firebase.tsx b/libs/components/account/src/login/Firebase.tsx
deleted file mode 100644
index 8bf8ca563..000000000
--- a/libs/components/account/src/login/Firebase.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { initializeApp } from 'firebase/app';
-import {
- browserLocalPersistence,
- getAuth,
- GoogleAuthProvider,
- signInWithPopup,
-} from 'firebase/auth';
-import { useCallback, useMemo } from 'react';
-
-import { MuiButton } from '@toeverything/components/ui';
-
-const _firebaseConfig = {
- apiKey: 'AIzaSyD7A_VyGaKTXsPqtga9IbwrEsbWWc4rH3Y',
- authDomain: 'login.affine.pro',
- projectId: 'affine-346417',
- storageBucket: 'affine-346417.appspot.com',
- messagingSenderId: '690608236388',
- appId: '1:690608236388:web:ccc7ee97b59a4cf2677c71',
-};
-
-const _app = initializeApp(_firebaseConfig);
-
-const GoogleIcon = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
-
-export const Firebase = (props: { onError: () => void }) => {
- const [auth, provider] = useMemo(() => {
- const auth = getAuth(_app);
- auth.setPersistence(browserLocalPersistence);
- const provider = new GoogleAuthProvider();
- return [auth, provider];
- }, []);
-
- const handleAuth = useCallback(() => {
- signInWithPopup(auth, provider).catch(error => {
- const errorCode = error.code;
- const errorMessage = error.message;
- const email = error.customData.email;
- const credential = GoogleAuthProvider.credentialFromError(error);
- console.log(errorCode, errorMessage, email, credential);
- props.onError();
- });
- }, [auth, props, provider]);
-
- return (
- }
- onClick={handleAuth}
- >
- Continue with Google
-
- );
-};
diff --git a/libs/components/account/src/login/index.tsx b/libs/components/account/src/login/index.tsx
deleted file mode 100644
index 5c80f3712..000000000
--- a/libs/components/account/src/login/index.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { LogoImg } from '@toeverything/components/common';
-import { MuiBox, MuiGrid, MuiSnackbar } from '@toeverything/components/ui';
-import { useCallback, useState } from 'react';
-
-import { Error } from './../error';
-// import { Authing } from './Authing';
-import { FileSystem } from './FileSystem';
-import { Firebase } from './Firebase';
-
-export function Login() {
- const [error, setError] = useState(false);
-
- const onError = useCallback(() => {
- setError(true);
- setTimeout(() => setError(false), 3000);
- }, []);
-
- return (
-
-
-
-
-
-
-
-
-
- {(process.env['NX_LOCAL'] && (
-
- )) ||
- null}
- {(!process.env['NX_LOCAL'] && (
-
- )) ||
- null}
-
-
-
- );
-}
diff --git a/libs/components/account/tsconfig.json b/libs/components/account/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/account/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/account/tsconfig.lib.json b/libs/components/account/tsconfig.lib.json
deleted file mode 100644
index 0dda33329..000000000
--- a/libs/components/account/tsconfig.lib.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/account/tsconfig.spec.json b/libs/components/account/tsconfig.spec.json
deleted file mode 100644
index ee70363dc..000000000
--- a/libs/components/account/tsconfig.spec.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/affine-board/.babelrc b/libs/components/affine-board/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/affine-board/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/affine-board/.eslintrc.json b/libs/components/affine-board/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/affine-board/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/affine-board/README.md b/libs/components/affine-board/README.md
deleted file mode 100644
index ee741cabf..000000000
--- a/libs/components/affine-board/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-affine-board
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test components-affine-board` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/affine-board/jest.config.ts b/libs/components/affine-board/jest.config.ts
deleted file mode 100644
index 596406b47..000000000
--- a/libs/components/affine-board/jest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'components-affine-board',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/affine-board',
-};
diff --git a/libs/components/affine-board/package.json b/libs/components/affine-board/package.json
deleted file mode 100644
index 0e542aaef..000000000
--- a/libs/components/affine-board/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "@toeverything/components/affine-board",
- "version": "0.0.1",
- "license": "MIT"
-}
diff --git a/libs/components/affine-board/project.json b/libs/components/affine-board/project.json
deleted file mode 100644
index 850e9803e..000000000
--- a/libs/components/affine-board/project.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/components/affine-board/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/affine-board",
- "tsConfig": "libs/components/affine-board/tsconfig.lib.json",
- "project": "libs/components/affine-board/package.json",
- "entryFile": "libs/components/affine-board/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/affine-board/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/affine-board/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/affine-board"],
- "options": {
- "jestConfig": "libs/components/affine-board/jest.config.ts",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/affine-board/src/Board.tsx b/libs/components/affine-board/src/Board.tsx
deleted file mode 100644
index a28d3580d..000000000
--- a/libs/components/affine-board/src/Board.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import { createEditor } from '@toeverything/components/affine-editor';
-import * as commands from '@toeverything/components/board-commands';
-import { Tldraw } from '@toeverything/components/board-draw';
-import { getSession } from '@toeverything/components/board-sessions';
-import { deepCopy, TldrawApp } from '@toeverything/components/board-state';
-import { tools } from '@toeverything/components/board-tools';
-import { TDShapeType } from '@toeverything/components/board-types';
-import { RecastBlockProvider } from '@toeverything/components/editor-core';
-import { services } from '@toeverything/datasource/db-service';
-import { AsyncBlock, BlockEditor } from '@toeverything/framework/virgo';
-import { useEffect, useState } from 'react';
-import { useShapes } from './hooks';
-
-interface AffineBoardProps {
- workspace: string;
- rootBlockId: string;
-}
-
-const AffineBoard = ({
- workspace,
- rootBlockId,
- editor,
-}: AffineBoardProps & { editor: BlockEditor }) => {
- const [app, set_app] = useState();
- const [document] = useState(() => {
- return {
- ...deepCopy(TldrawApp.default_document),
- id: workspace,
- pages: {
- [rootBlockId]: {
- id: rootBlockId,
- name: `Page ${rootBlockId}`,
- childIndex: 1,
- shapes: {},
- bindings: {},
- },
- },
- pageStates: {
- [rootBlockId]: {
- id: rootBlockId,
- camera: {
- point: [0, 0],
- zoom: 1,
- },
- selectedIds: [],
- },
- },
- };
- });
-
- const { shapes } = useShapes(workspace, rootBlockId);
- useEffect(() => {
- if (app) {
- app.replacePageContent(shapes || {}, {}, {});
- }
- }, [app, shapes]);
-
- return (
- {
- if (shape === undefined) {
- return services.api.editorBlock.delete({
- workspace: workspace,
- id,
- });
- } else {
- let block = (
- await services.api.editorBlock.get({
- workspace: workspace,
- ids: [shape.affineId],
- })
- )?.[0];
- if (!block) {
- block =
- await services.api.editorBlock.create({
- workspace: workspace,
- parentId:
- app.appState.currentPageId,
- type:
- shape.type ===
- TDShapeType.Editor
- ? 'group'
- : 'shape',
- });
- }
- shape.affineId = block.id;
- return await services.api.editorBlock.update({
- workspace: shape.workspace,
- id: block.id,
- properties: {
- shapeProps: {
- value: JSON.stringify(shape),
- },
- },
- });
- }
- })
- );
- await services.api.editorBlock.suspend(workspace, false);
- },
- }}
- />
- );
-};
-
-export const AffineBoardWitchContext = ({
- workspace,
- rootBlockId,
-}: AffineBoardProps) => {
- const [editor, setEditor] = useState();
- useEffect(() => {
- const innerEditor = createEditor(workspace, rootBlockId, true);
- setEditor(innerEditor);
- return () => {
- innerEditor.dispose();
- };
- }, [workspace, rootBlockId]);
-
- const [page, setPage] = useState();
- useEffect(() => {
- editor?.getBlockById(rootBlockId).then(block => {
- setPage(block);
- });
- }, [editor, rootBlockId]);
- return page ? (
-
-
-
- ) : null;
-};
diff --git a/libs/components/affine-board/src/hooks/index.ts b/libs/components/affine-board/src/hooks/index.ts
deleted file mode 100644
index cda5da3de..000000000
--- a/libs/components/affine-board/src/hooks/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './use-shapes';
diff --git a/libs/components/affine-board/src/hooks/use-shapes.ts b/libs/components/affine-board/src/hooks/use-shapes.ts
deleted file mode 100644
index 818accb02..000000000
--- a/libs/components/affine-board/src/hooks/use-shapes.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { defaultStyle, Editor } from '@toeverything/components/board-shapes';
-import type { TDShape } from '@toeverything/components/board-types';
-import type { ReturnEditorBlock } from '@toeverything/datasource/db-service';
-import { services } from '@toeverything/datasource/db-service';
-import { usePageClientWidth } from '@toeverything/datasource/state';
-import { useEffect, useState } from 'react';
-
-export const useShapes = (workspace: string, rootBlockId: string) => {
- const { pageClientWidth } = usePageClientWidth();
- // page padding left and right total 300px
- const editorShapeInitSize = pageClientWidth - 300;
- const [blocks, setBlocks] = useState<{
- shapes: [ReturnEditorBlock[]];
- }>();
-
- useEffect(() => {
- const unobservesMap = new Map();
- const observeChild = async (childId: string) => {
- unobservesMap.set(
- childId,
- await services.api.editorBlock.observe(
- { workspace, id: childId },
- blockData => {
- setBlocks(data => {
- const blockShapes = data?.shapes?.[0];
- const idx =
- blockShapes.findIndex(
- s => s.id === blockData.id
- ) || -1;
- const newBlockShapes = [...blockShapes];
- if (idx > -1) {
- newBlockShapes[idx] = blockData;
- } else {
- newBlockShapes.push(blockData);
- }
- return {
- shapes: [newBlockShapes],
- };
- });
- }
- )
- );
- };
- Promise.all([
- services.api.editorBlock
- .get({ workspace, ids: [rootBlockId] })
- .then(async blockData => {
- const shapes = await Promise.all(
- (blockData?.[0]?.children || []).map(async childId => {
- const childBlock = (
- await services.api.editorBlock.get({
- workspace,
- ids: [childId],
- })
- )?.[0];
- observeChild(childBlock.id);
- return childBlock;
- })
- );
- return shapes;
- }),
- ]).then(shapes => {
- setBlocks({
- shapes: shapes,
- });
- });
-
- let unobserve: () => void;
- services.api.editorBlock
- .observe({ workspace, id: rootBlockId }, async blockData => {
- Promise.all(
- (blockData?.children || []).map(async childId => {
- const childBlock = (
- await services.api.editorBlock.get({
- workspace,
- ids: [childId],
- })
- )?.[0];
- if (!unobservesMap.has(childBlock.id)) {
- observeChild(childBlock.id);
- }
- return childBlock;
- })
- ).then(shapes => {
- setBlocks({
- shapes: [shapes],
- });
- });
- })
- .then(cb => {
- unobserve = cb;
- });
-
- return () => {
- unobserve?.();
- };
- }, [workspace, rootBlockId]);
-
- let groupCount = 0;
- const blocksShapes = blocks?.shapes[0]?.reduce((acc, block) => {
- const shapeProps = block.properties.shapeProps?.value
- ? JSON.parse(block.properties.shapeProps.value)
- : {};
- if (block.type === 'shape') {
- acc[block.id] = {
- type: 'rectangle',
- size: [0, 0],
- point: [0, 0],
- parentId: rootBlockId,
- ...shapeProps,
- id: block.id,
- style: { ...defaultStyle, ...(shapeProps.style || {}) },
- workspace,
- };
- } else {
- acc[block.id] = Editor.getShape({
- point: [groupCount * editorShapeInitSize + 200, 200],
- id: block.id,
- size: [editorShapeInitSize, 200],
- parentId: rootBlockId,
- ...shapeProps,
- affineId: shapeProps.affineId ?? block.id,
- workspace: block.workspace,
- rootBlockId: block.id,
- });
- groupCount = groupCount + 1;
- }
-
- return acc;
- }, {} as Record);
-
- return {
- shapes: blocksShapes,
- };
-};
diff --git a/libs/components/affine-board/src/index.ts b/libs/components/affine-board/src/index.ts
deleted file mode 100644
index f50e49fea..000000000
--- a/libs/components/affine-board/src/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { AffineBoardWitchContext as AffineBoard } from './Board';
diff --git a/libs/components/affine-board/tsconfig.json b/libs/components/affine-board/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/affine-board/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/affine-board/tsconfig.lib.json b/libs/components/affine-board/tsconfig.lib.json
deleted file mode 100644
index 6a4378740..000000000
--- a/libs/components/affine-board/tsconfig.lib.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "jest.config.ts",
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/affine-board/tsconfig.spec.json b/libs/components/affine-board/tsconfig.spec.json
deleted file mode 100644
index 24b3ac659..000000000
--- a/libs/components/affine-board/tsconfig.spec.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "jest.config.ts",
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/affine-editor/.babelrc b/libs/components/affine-editor/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/affine-editor/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/affine-editor/.eslintrc.json b/libs/components/affine-editor/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/affine-editor/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/affine-editor/jest.config.js b/libs/components/affine-editor/jest.config.js
deleted file mode 100644
index b3a90e131..000000000
--- a/libs/components/affine-editor/jest.config.js
+++ /dev/null
@@ -1,9 +0,0 @@
-module.exports = {
- displayName: 'components-affine-editor',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/affine-editor',
-};
diff --git a/libs/components/affine-editor/package.json b/libs/components/affine-editor/package.json
deleted file mode 100644
index 1d2db5b28..000000000
--- a/libs/components/affine-editor/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "name": "@toeverything/components/affine-editor",
- "version": "0.0.1",
- "license": "MIT"
-}
diff --git a/libs/components/affine-editor/project.json b/libs/components/affine-editor/project.json
deleted file mode 100644
index c47bcd21a..000000000
--- a/libs/components/affine-editor/project.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "sourceRoot": "libs/components/affine-editor/src",
- "projectType": "library",
- "tags": ["components:affine-editor"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/affine-editor",
- "tsConfig": "libs/components/affine-editor/tsconfig.lib.json",
- "project": "libs/components/affine-editor/package.json",
- "entryFile": "libs/components/affine-editor/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/affine-editor/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/affine-editor/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/affine-editor"],
- "options": {
- "jestConfig": "libs/components/affine-editor/jest.config.js",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/affine-editor/src/Editor.tsx b/libs/components/affine-editor/src/Editor.tsx
deleted file mode 100644
index f1edb2ce7..000000000
--- a/libs/components/affine-editor/src/Editor.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
-
-import {
- RenderRoot,
- type BlockEditor,
-} from '@toeverything/components/editor-core';
-import { useCurrentEditors } from '@toeverything/datasource/state';
-
-import { createEditor } from './create-editor';
-
-interface AffineEditorProps {
- workspace: string;
- rootBlockId: string;
- /**
- * Whether to show the visual blank at the bottom of the article
- */
- scrollBlank?: boolean;
-
- isEdgeless?: boolean;
-
- scrollContainer?: HTMLElement;
- scrollController?: {
- lockScroll: () => void;
- unLockScroll: () => void;
- };
-}
-
-function _useConstantWithDispose(
- workspace: string,
- rootBlockId: string,
- isEdgeless: boolean
-) {
- const ref = useRef<{ data: BlockEditor; onInit: boolean }>(null);
- const { setCurrentEditors } = useCurrentEditors();
- ref.current ??= {
- data: createEditor(workspace, rootBlockId, isEdgeless),
- onInit: true,
- };
-
- useEffect(() => {
- if (ref.current.onInit) {
- ref.current.onInit = false;
- } else {
- ref.current.data = createEditor(workspace, rootBlockId, isEdgeless);
- }
- setCurrentEditors(prev => ({
- ...prev,
- [rootBlockId]: ref.current.data,
- }));
- return () => ref.current.data.dispose();
- }, [workspace, rootBlockId, isEdgeless, setCurrentEditors]);
-
- return ref.current.data;
-}
-
-export const AffineEditor = forwardRef(
- (
- {
- workspace,
- rootBlockId,
- scrollBlank = true,
- isEdgeless,
- scrollController,
- scrollContainer,
- },
- ref
- ) => {
- const editor = _useConstantWithDispose(
- workspace,
- rootBlockId,
- isEdgeless
- );
-
- useEffect(() => {
- if (scrollContainer) {
- editor.scrollManager.scrollContainer = scrollContainer;
- }
- if (scrollController) {
- editor.scrollManager.scrollController = scrollController;
- }
- }, [editor, scrollContainer, scrollController]);
-
- useImperativeHandle(ref, () => editor);
-
- return (
-
- );
- }
-);
diff --git a/libs/components/affine-editor/src/create-editor.ts b/libs/components/affine-editor/src/create-editor.ts
deleted file mode 100644
index 052cfbc31..000000000
--- a/libs/components/affine-editor/src/create-editor.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import {
- BulletBlock,
- CalloutBlock,
- CodeBlock,
- DividerBlock,
- EmbedLinkBlock,
- FigmaBlock,
- // TocBlock,
- FileBlock,
- GridBlock,
- GridItemBlock,
- GroupBlock,
- GroupDividerBlock,
- Heading1Block,
- Heading2Block,
- Heading3Block,
- ImageBlock,
- NumberedBlock,
- PageBlock,
- QuoteBlock,
- RefLinkBlock,
- TextBlock,
- TodoBlock,
- YoutubeBlock,
-} from '@toeverything/components/editor-blocks';
-import { plugins } from '@toeverything/components/editor-plugins';
-import { Protocol } from '@toeverything/datasource/db-service';
-import { BlockEditor } from '@toeverything/framework/virgo';
-
-export const createEditor = (
- workspace: string,
- rootBlockId: string,
- isEdgeless?: boolean
-) => {
- const blockEditor = new BlockEditor({
- workspace,
- rootBlockId,
- views: {
- [Protocol.Block.Type.page]: new PageBlock(),
- [Protocol.Block.Type.reference]: new RefLinkBlock(),
- [Protocol.Block.Type.code]: new CodeBlock(),
- [Protocol.Block.Type.text]: new TextBlock(),
- [Protocol.Block.Type.heading1]: new Heading1Block(),
- [Protocol.Block.Type.heading2]: new Heading2Block(),
- [Protocol.Block.Type.heading3]: new Heading3Block(),
- [Protocol.Block.Type.quote]: new QuoteBlock(),
- [Protocol.Block.Type.todo]: new TodoBlock(),
- // [Protocol.Block.Type.toc]: new TocBlock(),
- [Protocol.Block.Type.file]: new FileBlock(),
- [Protocol.Block.Type.image]: new ImageBlock(),
- [Protocol.Block.Type.divider]: new DividerBlock(),
- [Protocol.Block.Type.callout]: new CalloutBlock(),
- [Protocol.Block.Type.youtube]: new YoutubeBlock(),
- [Protocol.Block.Type.figma]: new FigmaBlock(),
- [Protocol.Block.Type.group]: new GroupBlock(),
- [Protocol.Block.Type.embedLink]: new EmbedLinkBlock(),
- [Protocol.Block.Type.numbered]: new NumberedBlock(),
- [Protocol.Block.Type.bullet]: new BulletBlock(),
- [Protocol.Block.Type.grid]: new GridBlock(),
- [Protocol.Block.Type.gridItem]: new GridItemBlock(),
- [Protocol.Block.Type.groupDivider]: new GroupDividerBlock(),
- },
- plugins,
- isEdgeless,
- });
-
- return blockEditor;
-};
diff --git a/libs/components/affine-editor/src/index.ts b/libs/components/affine-editor/src/index.ts
deleted file mode 100644
index 41a2f8b3d..000000000
--- a/libs/components/affine-editor/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { createEditor } from './create-editor';
-export { AffineEditor } from './Editor';
diff --git a/libs/components/affine-editor/tsconfig.json b/libs/components/affine-editor/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/affine-editor/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/affine-editor/tsconfig.lib.json b/libs/components/affine-editor/tsconfig.lib.json
deleted file mode 100644
index 0dda33329..000000000
--- a/libs/components/affine-editor/tsconfig.lib.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/affine-editor/tsconfig.spec.json b/libs/components/affine-editor/tsconfig.spec.json
deleted file mode 100644
index ee70363dc..000000000
--- a/libs/components/affine-editor/tsconfig.spec.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/board-commands/.babelrc b/libs/components/board-commands/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/board-commands/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/board-commands/.eslintrc.json b/libs/components/board-commands/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/board-commands/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/board-commands/README.md b/libs/components/board-commands/README.md
deleted file mode 100644
index 4b4c75d72..000000000
--- a/libs/components/board-commands/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-board-commands
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test components-board-commands` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/board-commands/jest.config.ts b/libs/components/board-commands/jest.config.ts
deleted file mode 100644
index 124fa9d90..000000000
--- a/libs/components/board-commands/jest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'components-board-commands',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/board-commands',
-};
diff --git a/libs/components/board-commands/package.json b/libs/components/board-commands/package.json
deleted file mode 100644
index bc5c66d45..000000000
--- a/libs/components/board-commands/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "@toeverything/components/board-commands",
- "version": "0.0.1",
- "license": "MIT",
- "dependencies": {
- "@tldraw/core": "^1.12.0",
- "@tldraw/intersect": "^1.7.1",
- "@tldraw/vec": "^1.7.0"
- }
-}
diff --git a/libs/components/board-commands/project.json b/libs/components/board-commands/project.json
deleted file mode 100644
index 111c84137..000000000
--- a/libs/components/board-commands/project.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/components/board-commands/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/board-commands",
- "tsConfig": "libs/components/board-commands/tsconfig.lib.json",
- "project": "libs/components/board-commands/package.json",
- "entryFile": "libs/components/board-commands/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/board-commands/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/board-commands/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/board-commands"],
- "options": {
- "jestConfig": "libs/components/board-commands/jest.config.ts",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/board-commands/src/align-shapes.ts b/libs/components/board-commands/src/align-shapes.ts
deleted file mode 100644
index 3e798d20a..000000000
--- a/libs/components/board-commands/src/align-shapes.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- AlignType,
- TDShapeType,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function alignShapes(
- app: TldrawApp,
- ids: string[],
- type: AlignType
-): TldrawCommand {
- const { currentPageId } = app;
-
- const initialShapes = ids.map(id => app.getShape(id));
-
- const boundsForShapes = initialShapes.map(shape => {
- return {
- id: shape.id,
- point: [...shape.point],
- bounds: TLDR.get_bounds(shape),
- };
- });
-
- const commonBounds = Utils.getCommonBounds(
- boundsForShapes.map(({ bounds }) => bounds)
- );
-
- const midX = commonBounds.minX + commonBounds.width / 2;
- const midY = commonBounds.minY + commonBounds.height / 2;
-
- const deltaMap = Object.fromEntries(
- boundsForShapes.map(({ id, point, bounds }) => {
- return [
- id,
- {
- prev: point,
- next: {
- [AlignType.CenterVertical]: [
- point[0],
- midY - bounds.height / 2,
- ],
- [AlignType.CenterHorizontal]: [
- midX - bounds.width / 2,
- point[1],
- ],
- [AlignType.Top]: [point[0], commonBounds.minY],
- [AlignType.Bottom]: [
- point[0],
- commonBounds.maxY - bounds.height,
- ],
- [AlignType.Left]: [commonBounds.minX, point[1]],
- [AlignType.Right]: [
- commonBounds.maxX - bounds.width,
- point[1],
- ],
- }[type],
- },
- ];
- })
- );
-
- const { before, after } = TLDR.mutate_shapes(
- app.state,
- ids,
- shape => {
- if (!deltaMap[shape.id]) return shape;
- return { point: deltaMap[shape.id].next };
- },
- currentPageId
- );
-
- initialShapes.forEach(shape => {
- if (shape.type === TDShapeType.Group) {
- const delta = Vec.sub(
- after[shape.id].point!,
- before[shape.id].point!
- );
-
- shape.children.forEach(id => {
- const child = app.getShape(id);
- before[child.id] = { point: child.point };
- after[child.id] = { point: Vec.add(child.point, delta) };
- });
-
- delete before[shape.id];
- delete after[shape.id];
- }
- });
-
- return {
- id: 'align',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: before,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: after,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/change-page.ts b/libs/components/board-commands/src/change-page.ts
deleted file mode 100644
index 787c29dca..000000000
--- a/libs/components/board-commands/src/change-page.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-
-export function changePage(app: TldrawApp, pageId: string): TldrawCommand {
- return {
- id: 'change_page',
- before: {
- appState: {
- currentPageId: app.currentPageId,
- },
- },
- after: {
- appState: {
- currentPageId: pageId,
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/create-page.ts b/libs/components/board-commands/src/create-page.ts
deleted file mode 100644
index e6255bf40..000000000
--- a/libs/components/board-commands/src/create-page.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { TLPageState, Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- TDPage,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function createPage(
- app: TldrawApp,
- center: number[],
- pageId = Utils.uniqueId()
-): TldrawCommand {
- const { currentPageId } = app;
-
- const topPage = Object.values(app.state.document.pages).sort(
- (a, b) => (b.childIndex || 0) - (a.childIndex || 0)
- )[0];
-
- const nextChildIndex = topPage?.childIndex ? topPage?.childIndex + 1 : 1;
-
- // TODO: Iterate the name better
- const nextName = `New Page`;
-
- const page: TDPage = {
- id: pageId,
- name: nextName,
- childIndex: nextChildIndex,
- shapes: {},
- bindings: {},
- };
-
- const pageState: TLPageState = {
- id: pageId,
- selectedIds: [],
- camera: { point: center, zoom: 1 },
- editingId: undefined,
- bindingId: undefined,
- hoveredId: undefined,
- pointedId: undefined,
- };
-
- return {
- id: 'create_page',
- before: {
- appState: {
- currentPageId,
- },
- document: {
- pages: {
- [pageId]: undefined,
- },
- pageStates: {
- [pageId]: undefined,
- },
- },
- },
- after: {
- appState: {
- currentPageId: page.id,
- },
- document: {
- pages: {
- [pageId]: page,
- },
- pageStates: {
- [pageId]: pageState,
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/create-shapes.ts b/libs/components/board-commands/src/create-shapes.ts
deleted file mode 100644
index 6ebff1ca0..000000000
--- a/libs/components/board-commands/src/create-shapes.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- Patch,
- TDBinding,
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function createShapes(
- app: TldrawApp,
- shapes: TDShape[],
- bindings: TDBinding[] = []
-): TldrawCommand {
- const { currentPageId } = app;
-
- const beforeShapes: Record | undefined> = {};
- const afterShapes: Record | undefined> = {};
-
- shapes.forEach(shape => {
- beforeShapes[shape.id] = undefined;
- afterShapes[shape.id] = shape;
- });
-
- const beforeBindings: Record | undefined> = {};
- const afterBindings: Record | undefined> = {};
-
- bindings.forEach(binding => {
- beforeBindings[binding.id] = undefined;
- afterBindings[binding.id] = binding;
- });
-
- return {
- id: 'create',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: [...app.selectedIds],
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- bindings: afterBindings,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: shapes.map(shape => shape.id),
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/delete-page.ts b/libs/components/board-commands/src/delete-page.ts
deleted file mode 100644
index 0cde6bf91..000000000
--- a/libs/components/board-commands/src/delete-page.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-
-export function deletePage(app: TldrawApp, pageId: string): TldrawCommand {
- const {
- currentPageId,
- document: { pages, pageStates },
- } = app;
-
- const pagesArr = Object.values(pages).sort(
- (a, b) => (a.childIndex || 0) - (b.childIndex || 0)
- );
-
- const currentIndex = pagesArr.findIndex(page => page.id === pageId);
-
- let nextCurrentPageId: string;
-
- if (pageId === currentPageId) {
- if (currentIndex === pagesArr.length - 1) {
- nextCurrentPageId = pagesArr[pagesArr.length - 2].id;
- } else {
- nextCurrentPageId = pagesArr[currentIndex + 1].id;
- }
- } else {
- nextCurrentPageId = currentPageId;
- }
-
- return {
- id: 'delete_page',
- before: {
- appState: {
- currentPageId: pageId,
- },
- document: {
- pages: {
- [pageId]: { ...pages[pageId] },
- },
- pageStates: {
- [pageId]: { ...pageStates[pageId] },
- },
- },
- },
- after: {
- appState: {
- currentPageId: nextCurrentPageId,
- },
- document: {
- pages: {
- [pageId]: undefined,
- },
- pageStates: {
- [pageId]: undefined,
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/delete-shapes.ts b/libs/components/board-commands/src/delete-shapes.ts
deleted file mode 100644
index 4b3407bbc..000000000
--- a/libs/components/board-commands/src/delete-shapes.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- TDAsset,
- TDAssets,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-import { removeShapesFromPage } from './shared/remove-shapes-from-page';
-
-const removeAssetsFromDocument = (assets: TDAssets, idsToRemove: string[]) => {
- const afterAssets: Record = { ...assets };
- idsToRemove.forEach(id => (afterAssets[id] = undefined));
- return afterAssets;
-};
-
-export function deleteShapes(
- app: TldrawApp,
- ids: string[],
- pageId = app.currentPageId
-): TldrawCommand {
- const {
- pageState,
- selectedIds,
- document: { assets: beforeAssets },
- } = app;
- const { before, after, assetsToRemove } = removeShapesFromPage(
- app.state,
- ids,
- pageId
- );
- const afterAssets = removeAssetsFromDocument(beforeAssets, assetsToRemove);
-
- return {
- id: 'delete',
- before: {
- document: {
- assets: beforeAssets,
- pages: {
- [pageId]: before,
- },
- pageStates: {
- [pageId]: { selectedIds: [...app.selectedIds] },
- },
- },
- },
- after: {
- document: {
- assets: afterAssets,
- pages: {
- [pageId]: after,
- },
- pageStates: {
- [pageId]: {
- selectedIds: selectedIds.filter(
- id => !ids.includes(id)
- ),
- hoveredId:
- pageState.hoveredId &&
- ids.includes(pageState.hoveredId)
- ? undefined
- : pageState.hoveredId,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/distribute-shapes.ts b/libs/components/board-commands/src/distribute-shapes.ts
deleted file mode 100644
index e6bc5256e..000000000
--- a/libs/components/board-commands/src/distribute-shapes.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- DistributeType,
- TDShape,
- TDShapeType,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function distributeShapes(
- app: TldrawApp,
- ids: string[],
- type: DistributeType
-): TldrawCommand {
- const { currentPageId } = app;
-
- const initialShapes = ids.map(id => app.getShape(id));
-
- const deltaMap = Object.fromEntries(
- getDistributions(initialShapes, type).map(d => [d.id, d])
- );
-
- const { before, after } = TLDR.mutate_shapes(
- app.state,
- ids.filter(id => deltaMap[id] !== undefined),
- shape => ({ point: deltaMap[shape.id].next }),
- currentPageId
- );
-
- initialShapes.forEach(shape => {
- if (shape.type === TDShapeType.Group) {
- const delta = Vec.sub(
- after[shape.id].point!,
- before[shape.id].point!
- );
-
- shape.children.forEach(id => {
- const child = app.getShape(id);
- before[child.id] = { point: child.point };
- after[child.id] = { point: Vec.add(child.point, delta) };
- });
-
- delete before[shape.id];
- delete after[shape.id];
- }
- });
-
- return {
- id: 'distribute',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: before },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: after },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
-
-function getDistributions(initialShapes: TDShape[], type: DistributeType) {
- const entries = initialShapes.map(shape => {
- const utils = TLDR.get_shape_util(shape);
- return {
- id: shape.id,
- point: [...shape.point],
- bounds: utils.getBounds(shape),
- center: utils.getCenter(shape),
- };
- });
-
- const len = entries.length;
- const commonBounds = Utils.getCommonBounds(
- entries.map(({ bounds }) => bounds)
- );
-
- const results: { id: string; prev: number[]; next: number[] }[] = [];
-
- switch (type) {
- case DistributeType.Horizontal: {
- const span = entries.reduce((a, c) => a + c.bounds.width, 0);
-
- if (span > commonBounds.width) {
- const left = entries.sort(
- (a, b) => a.bounds.minX - b.bounds.minX
- )[0];
-
- const right = entries.sort(
- (a, b) => b.bounds.maxX - a.bounds.maxX
- )[0];
-
- const entriesToMove = entries
- .filter(a => a !== left && a !== right)
- .sort((a, b) => a.center[0] - b.center[0]);
-
- const step = (right.center[0] - left.center[0]) / (len - 1);
-
- const x = left.center[0] + step;
-
- entriesToMove.forEach(({ id, point, bounds }, i) => {
- results.push({
- id,
- prev: point,
- next: [x + step * i - bounds.width / 2, bounds.minY],
- });
- });
- } else {
- const entriesToMove = entries.sort(
- (a, b) => a.center[0] - b.center[0]
- );
-
- let x = commonBounds.minX;
- const step = (commonBounds.width - span) / (len - 1);
-
- entriesToMove.forEach(({ id, point, bounds }) => {
- results.push({ id, prev: point, next: [x, bounds.minY] });
- x += bounds.width + step;
- });
- }
- break;
- }
- case DistributeType.Vertical: {
- const span = entries.reduce((a, c) => a + c.bounds.height, 0);
-
- if (span > commonBounds.height) {
- const top = entries.sort(
- (a, b) => a.bounds.minY - b.bounds.minY
- )[0];
-
- const bottom = entries.sort(
- (a, b) => b.bounds.maxY - a.bounds.maxY
- )[0];
-
- const entriesToMove = entries
- .filter(a => a !== top && a !== bottom)
- .sort((a, b) => a.center[1] - b.center[1]);
-
- const step = (bottom.center[1] - top.center[1]) / (len - 1);
-
- const y = top.center[1] + step;
-
- entriesToMove.forEach(({ id, point, bounds }, i) => {
- results.push({
- id,
- prev: point,
- next: [bounds.minX, y + step * i - bounds.height / 2],
- });
- });
- } else {
- const entriesToMove = entries.sort(
- (a, b) => a.center[1] - b.center[1]
- );
-
- let y = commonBounds.minY;
- const step = (commonBounds.height - span) / (len - 1);
-
- entriesToMove.forEach(({ id, point, bounds }) => {
- results.push({ id, prev: point, next: [bounds.minX, y] });
- y += bounds.height + step;
- });
- }
-
- break;
- }
- }
-
- return results;
-}
diff --git a/libs/components/board-commands/src/duplicate-page.ts b/libs/components/board-commands/src/duplicate-page.ts
deleted file mode 100644
index 2ca092a8b..000000000
--- a/libs/components/board-commands/src/duplicate-page.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-
-export function duplicatePage(app: TldrawApp, pageId: string): TldrawCommand {
- const newId = Utils.uniqueId();
- const {
- currentPageId,
- page,
- pageState: { camera },
- } = app;
-
- const nextPage = {
- ...page,
- id: newId,
- name: page.name + ' Copy',
- shapes: Object.fromEntries(
- Object.entries(page.shapes).map(([id, shape]) => {
- return [
- id,
- {
- ...shape,
- parentId:
- shape.parentId === pageId ? newId : shape.parentId,
- },
- ];
- })
- ),
- };
-
- return {
- id: 'duplicate_page',
- before: {
- appState: {
- currentPageId,
- },
- document: {
- pages: {
- [newId]: undefined,
- },
- pageStates: {
- [newId]: undefined,
- },
- },
- },
- after: {
- appState: {
- currentPageId: newId,
- },
- document: {
- pages: {
- [newId]: nextPage,
- },
- pageStates: {
- [newId]: {
- ...page,
- id: newId,
- selectedIds: [],
- camera: { ...camera },
- editingId: undefined,
- bindingId: undefined,
- hoveredId: undefined,
- pointedId: undefined,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/duplicate-shapes.ts b/libs/components/board-commands/src/duplicate-shapes.ts
deleted file mode 100644
index eb058fee8..000000000
--- a/libs/components/board-commands/src/duplicate-shapes.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- PagePartial,
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function duplicateShapes(
- app: TldrawApp,
- ids: string[],
- point?: number[]
-): TldrawCommand {
- const { selectedIds, currentPageId, page, shapes } = app;
-
- const before: PagePartial = {
- shapes: {},
- bindings: {},
- };
-
- const after: PagePartial = {
- shapes: {},
- bindings: {},
- };
-
- const duplicateMap: Record = {};
-
- const shapesToDuplicate = ids
- .map(id => app.getShape(id))
- .filter(shape => !ids.includes(shape.parentId));
-
- // Create duplicates
- shapesToDuplicate.forEach(shape => {
- const duplicatedId = Utils.uniqueId();
- before.shapes[duplicatedId] = undefined;
-
- after.shapes[duplicatedId] = {
- ...Utils.deepClone(shape),
- id: duplicatedId,
- childIndex: TLDR.get_child_index_above(
- app.state,
- shape.id,
- currentPageId
- ),
- };
-
- if (shape.children) {
- after.shapes[duplicatedId]!.children = [];
- }
-
- if (shape.parentId !== currentPageId) {
- const parent = app.getShape(shape.parentId);
-
- before.shapes[parent.id] = {
- ...before.shapes[parent.id],
- children: parent.children,
- };
-
- after.shapes[parent.id] = {
- ...after.shapes[parent.id],
- children: [
- ...(after.shapes[parent.id] || parent).children!,
- duplicatedId,
- ],
- };
- }
-
- duplicateMap[shape.id] = duplicatedId;
- });
-
- // If the shapes have children, then duplicate those too
- shapesToDuplicate.forEach(shape => {
- if (shape.children) {
- shape.children.forEach(childId => {
- const child = app.getShape(childId);
- const duplicatedId = Utils.uniqueId();
- const duplicatedParentId = duplicateMap[shape.id];
- before.shapes[duplicatedId] = undefined;
- after.shapes[duplicatedId] = {
- ...Utils.deepClone(child),
- id: duplicatedId,
- parentId: duplicatedParentId,
- childIndex: TLDR.get_child_index_above(
- app.state,
- child.id,
- currentPageId
- ),
- };
- duplicateMap[childId] = duplicatedId;
- after.shapes[duplicateMap[shape.id]]?.children?.push(
- duplicatedId
- );
- });
- }
- });
-
- // Which ids did we end up duplicating?
- const dupedShapeIds = new Set(Object.keys(duplicateMap));
-
- // Handle bindings that effect duplicated shapes
- Object.values(page.bindings)
- .filter(
- binding =>
- dupedShapeIds.has(binding.fromId) ||
- dupedShapeIds.has(binding.toId)
- )
- .forEach(binding => {
- if (dupedShapeIds.has(binding.fromId)) {
- if (dupedShapeIds.has(binding.toId)) {
- // If the binding is between two duplicating shapes then
- // duplicate the binding, too
- const duplicatedBindingId = Utils.uniqueId();
-
- const duplicatedBinding = {
- ...Utils.deepClone(binding),
- id: duplicatedBindingId,
- fromId: duplicateMap[binding.fromId],
- toId: duplicateMap[binding.toId],
- };
-
- before.bindings[duplicatedBindingId] = undefined;
- after.bindings[duplicatedBindingId] = duplicatedBinding;
-
- // Change the duplicated shape's handle so that it reference
- // the duplicated binding
- const boundShape = after.shapes[duplicatedBinding.fromId];
- Object.values(boundShape!.handles!).forEach(handle => {
- if (handle!.bindingId === binding.id) {
- handle!.bindingId = duplicatedBindingId;
- }
- });
- } else {
- // If only the fromId is selected, delete the binding on
- // the duplicated shape's handles
- const boundShape =
- after.shapes[duplicateMap[binding.fromId]];
- Object.values(boundShape!.handles!).forEach(handle => {
- if (handle!.bindingId === binding.id) {
- handle!.bindingId = undefined;
- }
- });
- }
- }
- });
-
- // Now move the shapes
-
- const shapesToMove = Object.values(after.shapes) as TDShape[];
-
- if (point) {
- const commonBounds = Utils.getCommonBounds(
- shapesToMove.map(shape => TLDR.get_bounds(shape))
- );
- const center = Utils.getBoundsCenter(commonBounds);
- shapesToMove.forEach(shape => {
- // Could be a group
- if (!shape.point) return;
- shape.point = Vec.sub(point, Vec.sub(center, shape.point));
- });
- } else {
- const offset = [16, 16];
- shapesToMove.forEach(shape => {
- // Could be a group
- if (!shape.point) return;
- shape.point = Vec.add(shape.point, offset);
- });
- }
-
- // Unlock any locked shapes
- shapesToMove.forEach(shape => {
- if (shape.isLocked) {
- shape.isLocked = false;
- }
- });
-
- return {
- id: 'duplicate',
- before: {
- document: {
- pages: {
- [currentPageId]: before,
- },
- pageStates: {
- [currentPageId]: { selectedIds },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: after,
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: Array.from(dupedShapeIds.values()).map(
- id => duplicateMap[id]
- ),
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/flip-shapes.ts b/libs/components/board-commands/src/flip-shapes.ts
deleted file mode 100644
index 01837703c..000000000
--- a/libs/components/board-commands/src/flip-shapes.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { TLBoundsCorner, Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-import { FlipType } from '@toeverything/components/board-types';
-
-export function flipShapes(
- app: TldrawApp,
- ids: string[],
- type: FlipType
-): TldrawCommand {
- const { selectedIds, currentPageId, shapes } = app;
-
- const boundsForShapes = shapes.map(shape => TLDR.get_bounds(shape));
-
- const commonBounds = Utils.getCommonBounds(boundsForShapes);
-
- const { before, after } = TLDR.mutate_shapes(
- app.state,
- ids,
- shape => {
- const shapeBounds = TLDR.get_bounds(shape);
-
- switch (type) {
- case FlipType.Horizontal: {
- const newShapeBounds =
- Utils.getRelativeTransformedBoundingBox(
- commonBounds,
- commonBounds,
- shapeBounds,
- true,
- false
- );
-
- return TLDR.get_shape_util(shape).transform(
- shape,
- newShapeBounds,
- {
- type: TLBoundsCorner.TopLeft,
- scaleX: -1,
- scaleY: 1,
- initialShape: shape,
- transformOrigin: [0.5, 0.5],
- }
- );
- }
- case FlipType.Vertical: {
- const newShapeBounds =
- Utils.getRelativeTransformedBoundingBox(
- commonBounds,
- commonBounds,
- shapeBounds,
- false,
- true
- );
-
- return TLDR.get_shape_util(shape).transform(
- shape,
- newShapeBounds,
- {
- type: TLBoundsCorner.TopLeft,
- scaleX: 1,
- scaleY: -1,
- initialShape: shape,
- transformOrigin: [0.5, 0.5],
- }
- );
- }
- }
- },
- currentPageId
- );
-
- return {
- id: 'flip',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: before },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: after },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/group-shapes.ts b/libs/components/board-commands/src/group-shapes.ts
deleted file mode 100644
index 0e07e736f..000000000
--- a/libs/components/board-commands/src/group-shapes.ts
+++ /dev/null
@@ -1,254 +0,0 @@
-import { Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- Patch,
- TDBinding,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-import { TDShape, TDShapeType } from '@toeverything/components/board-types';
-
-export function groupShapes(
- app: TldrawApp,
- ids: string[],
- groupId: string,
- pageId: string
-): TldrawCommand | undefined {
- if (ids.length < 2) return;
-
- const beforeShapes: Record> = {};
- const afterShapes: Record> = {};
-
- const beforeBindings: Record> = {};
- const afterBindings: Record> = {};
-
- const idsToGroup = [...ids];
- const shapesToGroup: TDShape[] = [];
- const deletedGroupIds: string[] = [];
- const otherEffectedGroups: TDShape[] = [];
-
- // Collect all of the shapes to group (and their ids)
- for (const id of ids) {
- const shape = app.getShape(id);
- if (shape.isLocked) continue;
-
- if (shape.children === undefined) {
- shapesToGroup.push(shape);
- } else {
- const childIds = shape.children.filter(
- id => !app.getShape(id).isLocked
- );
- otherEffectedGroups.push(shape);
- idsToGroup.push(...childIds);
- shapesToGroup.push(
- ...childIds.map(id => app.getShape(id)).filter(Boolean)
- );
- }
- }
-
- // 1. Can we create this group?
-
- // Do the shapes have the same parent?
- if (
- shapesToGroup.every(
- shape => shape.parentId === shapesToGroup[0].parentId
- )
- ) {
- // Is the common parent a shape (not the page)?
- if (shapesToGroup[0].parentId !== pageId) {
- const commonParent = app.getShape(shapesToGroup[0].parentId);
- // Are all of the common parent's shapes selected?
- if (commonParent.children?.length === idsToGroup.length) {
- // Don't create a group if that group would be the same as the
- // existing group.
- return;
- }
- }
- }
-
- // A flattened array of shapes from the page
- const flattenedShapes = TLDR.flatten_page(app.state, pageId);
-
- // A map of shapes to their index in flattendShapes
- const shapeIndexMap = Object.fromEntries(
- shapesToGroup.map(shape => [shape.id, flattenedShapes.indexOf(shape)])
- );
-
- // An array of shapes in order by their index in flattendShapes
- const sortedShapes = shapesToGroup.sort(
- (a, b) => shapeIndexMap[a.id] - shapeIndexMap[b.id]
- );
-
- // The parentId is always the current page
- const groupParentId = pageId; // sortedShapes[0].parentId
-
- // The childIndex should be the lowest index of the selected shapes
- // with a parent that is the current page; or else the child index
- // of the lowest selected shape.
- const groupChildIndex = (
- sortedShapes.filter(shape => shape.parentId === pageId)[0] ||
- sortedShapes[0]
- ).childIndex;
-
- // The shape's point is the min point of its childrens' common bounds
- const groupBounds = Utils.getCommonBounds(
- shapesToGroup.map(shape => TLDR.get_bounds(shape))
- );
-
- // Create the group
- beforeShapes[groupId] = undefined;
-
- afterShapes[groupId] = TLDR.get_shape_util(TDShapeType.Group).create({
- id: groupId,
- affineId: groupId,
- childIndex: groupChildIndex,
- parentId: groupParentId,
- point: [groupBounds.minX, groupBounds.minY],
- size: [groupBounds.width, groupBounds.height],
- children: sortedShapes.map(shape => shape.id),
- workspace: app.document.id,
- });
-
- // Reparent shapes to the new group
- sortedShapes.forEach((shape, index) => {
- // If the shape is part of a different group, mark the parent shape for cleanup
- if (shape.parentId !== pageId) {
- const parentShape = app.getShape(shape.parentId);
- otherEffectedGroups.push(parentShape);
- }
-
- beforeShapes[shape.id] = {
- ...beforeShapes[shape.id],
- parentId: shape.parentId,
- childIndex: shape.childIndex,
- };
-
- afterShapes[shape.id] = {
- ...afterShapes[shape.id],
- parentId: groupId,
- childIndex: index + 1,
- };
- });
-
- // Clean up effected parents
- while (otherEffectedGroups.length > 0) {
- const shape = otherEffectedGroups.pop();
- if (!shape) break;
-
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const nextChildren = (beforeShapes[shape.id]?.children ||
- shape.children)!.filter(
- childId =>
- childId &&
- !(
- idsToGroup.includes(childId) ||
- deletedGroupIds.includes(childId)
- )
- );
-
- // If the parent has no children, remove it
- if (nextChildren.length === 0) {
- beforeShapes[shape.id] = shape;
- afterShapes[shape.id] = undefined;
-
- // And if that parent is part of a different group, mark it for cleanup
- // (This is necessary only when we implement nested groups.)
- if (shape.parentId !== pageId) {
- deletedGroupIds.push(shape.id);
- otherEffectedGroups.push(app.getShape(shape.parentId));
- }
- } else {
- beforeShapes[shape.id] = {
- ...beforeShapes[shape.id],
- children: shape.children,
- };
-
- afterShapes[shape.id] = {
- ...afterShapes[shape.id],
- children: nextChildren,
- };
- }
- }
-
- // TODO: This code is copied from delete.command. Create a shared helper!
-
- const { bindings } = app;
-
- const deletedGroupIdsSet = new Set(deletedGroupIds);
-
- // We also need to delete bindings that reference the deleted shapes
- bindings.forEach(binding => {
- for (const id of [binding.toId, binding.fromId]) {
- // If the binding references a deleted shape...
- if (deletedGroupIdsSet.has(id)) {
- // Delete this binding
- beforeBindings[binding.id] = binding;
- afterBindings[binding.id] = undefined;
-
- // Let's also look each the bound shape...
- const shape = app.getShape(id);
-
- // If the bound shape has a handle that references the deleted binding...
- if (shape.handles) {
- Object.values(shape.handles)
- .filter(handle => handle.bindingId === binding.id)
- .forEach(handle => {
- // Save the binding reference in the before patch
- beforeShapes[id] = {
- ...beforeShapes[id],
- handles: {
- ...beforeShapes[id]?.handles,
- [handle.id]: { bindingId: binding.id },
- },
- };
-
- // Unless we're currently deleting the shape, remove the
- // binding reference from the after patch
- if (!deletedGroupIds.includes(id)) {
- afterShapes[id] = {
- ...afterShapes[id],
- handles: {
- ...afterShapes[id]?.handles,
- [handle.id]: { bindingId: undefined },
- },
- };
- }
- });
- }
- }
- }
- });
- return {
- id: 'group',
- before: {
- document: {
- pages: {
- [pageId]: {
- shapes: beforeShapes,
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [pageId]: {
- shapes: afterShapes,
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: [groupId],
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/index.ts b/libs/components/board-commands/src/index.ts
deleted file mode 100644
index 84d5f7f41..000000000
--- a/libs/components/board-commands/src/index.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-export * from './align-shapes';
-export * from './change-page';
-export * from './create-page';
-export * from './create-shapes';
-export * from './delete-page';
-export * from './delete-shapes';
-export * from './distribute-shapes';
-export * from './duplicate-page';
-export * from './duplicate-shapes';
-export * from './flip-shapes';
-export * from './group-shapes';
-export * from './move-shapes-to-page';
-export * from './rename-page';
-export * from './reorder-shapes';
-export * from './reset-bounds';
-export * from './rotate-shapes';
-export * from './set-shapes-lock-status';
-export * from './set-shapes-props';
-export * from './stretch-shapes';
-export * from './style-shapes';
-export * from './toggle-shapes-decoration';
-export * from './toggle-shapes-prop';
-export * from './translate-shapes';
-export * from './ungroup-shapes';
-export * from './update-shapes';
diff --git a/libs/components/board-commands/src/move-shapes-to-page.ts b/libs/components/board-commands/src/move-shapes-to-page.ts
deleted file mode 100644
index 766a4a461..000000000
--- a/libs/components/board-commands/src/move-shapes-to-page.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { TLBounds, Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- ArrowShape,
- PagePartial,
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function moveShapesToPage(
- app: TldrawApp,
- ids: string[],
- viewportBounds: TLBounds,
- fromPageId: string,
- toPageId: string
-): TldrawCommand {
- const { page } = app;
-
- const fromPage: Record = {
- before: {
- shapes: {},
- bindings: {},
- },
- after: {
- shapes: {},
- bindings: {},
- },
- };
-
- const toPage: Record = {
- before: {
- shapes: {},
- bindings: {},
- },
- after: {
- shapes: {},
- bindings: {},
- },
- };
-
- // Collect all the shapes to move and their keys.
- const movingShapeIds = new Set();
- const shapesToMove = new Set();
-
- ids.map(id => app.getShape(id, fromPageId))
- .filter(shape => !shape.isLocked)
- .forEach(shape => {
- movingShapeIds.add(shape.id);
- shapesToMove.add(shape);
- if (shape.children !== undefined) {
- shape.children.forEach(childId => {
- movingShapeIds.add(childId);
- shapesToMove.add(app.getShape(childId, fromPageId));
- });
- }
- });
-
- // Where should we put start putting shapes on the "to" page?
- const startingChildIndex = TLDR.get_top_child_index(app.state, toPageId);
-
- // Which shapes are we moving?
- const movingShapes = Array.from(shapesToMove.values());
-
- movingShapes.forEach((shape, i) => {
- // Remove the shape from the fromPage
- fromPage['before'].shapes[shape.id] = shape;
- fromPage['after'].shapes[shape.id] = undefined;
-
- // But the moved shape on the "to" page
- toPage['before'].shapes[shape.id] = undefined;
- toPage['after'].shapes[shape.id] = shape;
-
- // If the shape's parent isn't moving too, reparent the shape to
- // the "to" page, at the top of the z stack
- if (!movingShapeIds.has(shape.parentId)) {
- toPage['after'].shapes[shape.id] = {
- ...shape,
- parentId: toPageId,
- childIndex: startingChildIndex + i,
- };
-
- // If the shape was in a group, then pull the shape from the
- // parent's children array.
- if (shape.parentId !== fromPageId) {
- const parent = app.getShape(shape.parentId, fromPageId);
- fromPage['before'].shapes[parent.id] = {
- children: parent.children,
- };
-
- fromPage['after'].shapes[parent.id] = {
- children: parent.children!.filter(
- childId => childId !== shape.id
- ),
- };
- }
- }
- });
-
- // Handle bindings that effect duplicated shapes
- Object.values(page.bindings)
- .filter(
- binding =>
- movingShapeIds.has(binding.fromId) ||
- movingShapeIds.has(binding.toId)
- )
- .forEach(binding => {
- // Always delete the binding from the from page
-
- fromPage['before'].bindings[binding.id] = binding;
- fromPage['after'].bindings[binding.id] = undefined;
-
- // Delete the reference from the binding's fromShape
-
- const fromBoundShape = app.getShape(binding.fromId, fromPageId);
-
- // Will we be copying this binding to the new page?
-
- const shouldCopy =
- movingShapeIds.has(binding.fromId) &&
- movingShapeIds.has(binding.toId);
-
- if (shouldCopy) {
- // Just move the binding to the new page
- toPage['before'].bindings[binding.id] = undefined;
- toPage['after'].bindings[binding.id] = binding;
- } else {
- if (movingShapeIds.has(binding.fromId)) {
- // If we are only moving the "from" shape, we need to delete
- // the binding reference from the "from" shapes handles
- const fromShape = app.getShape(binding.fromId, fromPageId);
- const handle = Object.values(fromBoundShape.handles!).find(
- handle => handle.bindingId === binding.id
- )!;
-
- // Remove the handle from the shape on the toPage
-
- const handleId = handle.id as keyof ArrowShape['handles'];
-
- const toPageShape = toPage['after'].shapes[fromShape.id]!;
-
- toPageShape.handles = {
- ...toPageShape.handles,
- [handleId]: {
- ...toPageShape.handles![handleId],
- bindingId: undefined,
- },
- };
- } else {
- // If we are only moving the "to" shape, we need to delete
- // the binding reference from the "from" shape's handles
- const fromShape = app.getShape(binding.fromId, fromPageId);
- const handle = Object.values(fromBoundShape.handles!).find(
- handle => handle.bindingId === binding.id
- )!;
-
- fromPage['before'].shapes[fromShape.id] = {
- handles: { [handle.id]: { bindingId: binding.id } },
- };
-
- fromPage['after'].shapes[fromShape.id] = {
- handles: { [handle.id]: { bindingId: undefined } },
- };
- }
- }
- });
-
- // Finally, center camera on selection
-
- const toPageState = app.state.document.pageStates[toPageId];
-
- const bounds = Utils.getCommonBounds(
- movingShapes.map(shape => TLDR.get_bounds(shape))
- );
-
- const zoom = TLDR.get_camera_zoom(
- viewportBounds.width < viewportBounds.height
- ? (viewportBounds.width - 128) / bounds.width
- : (viewportBounds.height - 128) / bounds.height
- );
-
- const mx = (viewportBounds.width - bounds.width * zoom) / 2 / zoom;
- const my = (viewportBounds.height - bounds.height * zoom) / 2 / zoom;
-
- const point = Vec.toFixed(Vec.add([-bounds.minX, -bounds.minY], [mx, my]));
-
- return {
- id: 'move_to_page',
- before: {
- appState: {
- currentPageId: fromPageId,
- },
- document: {
- pages: {
- [fromPageId]: fromPage['before'],
- [toPageId]: toPage['before'],
- },
- pageStates: {
- [fromPageId]: { selectedIds: ids },
- [toPageId]: {
- selectedIds: toPageState.selectedIds,
- camera: toPageState.camera,
- },
- },
- },
- },
- after: {
- appState: {
- currentPageId: toPageId,
- },
- document: {
- pages: {
- [fromPageId]: fromPage['after'],
- [toPageId]: toPage['after'],
- },
- pageStates: {
- [fromPageId]: { selectedIds: [] },
- [toPageId]: {
- selectedIds: ids,
- camera: {
- zoom,
- point,
- },
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/rename-page.ts b/libs/components/board-commands/src/rename-page.ts
deleted file mode 100644
index 2cccfdbe0..000000000
--- a/libs/components/board-commands/src/rename-page.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-
-export function renamePage(
- app: TldrawApp,
- pageId: string,
- name: string
-): TldrawCommand {
- const { page } = app;
-
- return {
- id: 'rename_page',
- before: {
- document: {
- pages: {
- [pageId]: { name: page.name },
- },
- },
- },
- after: {
- document: {
- pages: {
- [pageId]: { name: name },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/reorder-shapes.ts b/libs/components/board-commands/src/reorder-shapes.ts
deleted file mode 100644
index 929030446..000000000
--- a/libs/components/board-commands/src/reorder-shapes.ts
+++ /dev/null
@@ -1,265 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- MoveType,
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function reorderShapes(
- app: TldrawApp,
- ids: string[],
- type: MoveType
-): TldrawCommand {
- const { currentPageId, page } = app;
-
- // Get the unique parent ids for the selected elements
- const parentIds = new Set(ids.map(id => app.getShape(id).parentId));
-
- let result: {
- before: Record>;
- after: Record>;
- } = { before: {}, after: {} };
-
- let startIndex: number;
- let startChildIndex: number;
- let step: number;
-
- // Collect shapes with common parents into a table under their parent id
- Array.from(parentIds.values()).forEach(parentId => {
- let sortedChildren: TDShape[] = [];
- if (parentId === page.id) {
- sortedChildren = Object.values(page.shapes).sort(
- (a, b) => a.childIndex - b.childIndex
- );
- } else {
- const parent = app.getShape(parentId);
- if (!parent.children) throw Error('No children in parent!');
-
- sortedChildren = parent.children
- .map(childId => app.getShape(childId))
- .sort((a, b) => a.childIndex - b.childIndex);
- }
-
- const sortedChildIds = sortedChildren.map(shape => shape.id);
-
- const sortedIndicesToMove = ids
- .filter(id => sortedChildIds.includes(id))
- .map(id => sortedChildIds.indexOf(id))
- .sort((a, b) => a - b);
-
- if (sortedIndicesToMove.length === sortedChildIds.length) return;
-
- switch (type) {
- case MoveType.ToBack: {
- // a b c
- // Initial 1 2 3 4 5 6 7
- // Final .25 .5 .75 1 3 6 7
- // a b c
-
- // Find the lowest "open" index
- for (let i = 0; i < sortedChildIds.length; i++) {
- if (sortedIndicesToMove.includes(i)) continue;
- startIndex = i;
- break;
- }
-
- // Find the lowest child index that isn't in sortedIndicesToMove
- startChildIndex = sortedChildren[startIndex].childIndex;
-
- // Find the step for each additional child
- step = startChildIndex / (sortedIndicesToMove.length + 1);
-
- // Get the results of moving the selected shapes below the first open index's shape
- result = TLDR.mutate_shapes(
- app.state,
- sortedIndicesToMove
- .map(i => sortedChildren[i].id)
- .reverse(),
- (shape, i) => ({
- childIndex: startChildIndex - (i + 1) * step,
- }),
- currentPageId
- );
-
- break;
- }
- case MoveType.ToFront: {
- // a b c
- // Initial 1 2 3 4 5 6 7
- // Final 1 3 6 7 8 9 10
- // a b c
-
- // Find the highest "open" index
- for (let i = sortedChildIds.length - 1; i >= 0; i--) {
- if (sortedIndicesToMove.includes(i)) continue;
- startIndex = i;
- break;
- }
-
- // Find the lowest child index that isn't in sortedIndicesToMove
- startChildIndex = sortedChildren[startIndex].childIndex;
-
- // Find the step for each additional child
- step = 1;
-
- // Get the results of moving the selected shapes below the first open index's shape
- result = TLDR.mutate_shapes(
- app.state,
- sortedIndicesToMove.map(i => sortedChildren[i].id),
- (shape, i) => ({
- childIndex: startChildIndex + (i + 1),
- }),
- currentPageId
- );
-
- break;
- }
- case MoveType.Backward: {
- // a b c
- // Initial 1 2 3 4 5 6 7
- // Final .5 1 1.66 2.33 3 6 7
- // a b c
-
- const indexMap: Record = {};
-
- // Starting from the top...
- for (let i = sortedChildIds.length - 1; i >= 0; i--) {
- // If we found a moving index...
- if (sortedIndicesToMove.includes(i)) {
- for (let j = i; j >= 0; j--) {
- // iterate downward until we find an open spot
- if (!sortedIndicesToMove.includes(j)) {
- // i = the index of the first closed spot
- // j = the index of the first open spot
-
- const endChildIndex =
- sortedChildren[j].childIndex;
- let startChildIndex: number;
- let step: number;
-
- if (j === 0) {
- // We're moving below the first child, start from
- // half of its child index.
-
- startChildIndex = endChildIndex / 2;
- step = endChildIndex / 2 / (i - j + 1);
- } else {
- // Start from the child index of the child below the
- // child above.
- startChildIndex =
- sortedChildren[j - 1].childIndex;
- step =
- (endChildIndex - startChildIndex) /
- (i - j + 1);
- startChildIndex += step;
- }
-
- for (let k = 0; k < i - j; k++) {
- indexMap[sortedChildren[j + k + 1].id] =
- startChildIndex + step * k;
- }
-
- break;
- }
- }
- }
- }
-
- if (Object.values(indexMap).length > 0) {
- // Get the results of moving the selected shapes below the first open index's shape
- result = TLDR.mutate_shapes(
- app.state,
- sortedIndicesToMove.map(i => sortedChildren[i].id),
- shape => ({
- childIndex: indexMap[shape.id],
- }),
- currentPageId
- );
- }
-
- break;
- }
- case MoveType.Forward: {
- // a b c
- // Initial 1 2 3 4 5 6 7
- // Final 1 3 3.5 6 7 8 9
- // a b c
-
- const indexMap: Record = {};
-
- // Starting from the top...
- for (let i = 0; i < sortedChildIds.length; i++) {
- // If we found a moving index...
- if (sortedIndicesToMove.includes(i)) {
- // Search for the first open spot above this one
- for (let j = i; j < sortedChildIds.length; j++) {
- if (!sortedIndicesToMove.includes(j)) {
- // i = the low index of the first closed spot
- // j = the high index of the first open spot
-
- startChildIndex = sortedChildren[j].childIndex;
-
- const step =
- j === sortedChildIds.length - 1
- ? 1
- : (sortedChildren[j + 1].childIndex -
- startChildIndex) /
- (j - i + 1);
-
- for (let k = 0; k < j - i; k++) {
- indexMap[sortedChildren[i + k].id] =
- startChildIndex + step * (k + 1);
- }
-
- break;
- }
- }
- }
- }
-
- if (Object.values(indexMap).length > 0) {
- // Get the results of moving the selected shapes below the first open index's shape
- result = TLDR.mutate_shapes(
- app.state,
- sortedIndicesToMove.map(i => sortedChildren[i].id),
- shape => ({
- childIndex: indexMap[shape.id],
- }),
- currentPageId
- );
- }
-
- break;
- }
- }
- });
-
- return {
- id: 'move',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: result.before },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: result.after },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/reset-bounds.ts b/libs/components/board-commands/src/reset-bounds.ts
deleted file mode 100644
index a03383246..000000000
--- a/libs/components/board-commands/src/reset-bounds.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-
-export function resetBounds(
- app: TldrawApp,
- ids: string[],
- pageId: string
-): TldrawCommand {
- const { currentPageId } = app;
-
- const { before, after } = TLDR.mutate_shapes(
- app.state,
- ids,
- shape => app.getShapeUtil(shape).onDoubleClickBoundsHandle?.(shape),
- pageId
- );
-
- return {
- id: 'reset_bounds',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: before },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: after },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/rotate-shapes.ts b/libs/components/board-commands/src/rotate-shapes.ts
deleted file mode 100644
index 2e9b8444c..000000000
--- a/libs/components/board-commands/src/rotate-shapes.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-const PI2 = Math.PI * 2;
-
-export function rotateShapes(
- app: TldrawApp,
- ids: string[],
- delta = -PI2 / 4
-): TldrawCommand | void {
- const { currentPageId } = app;
-
- // The shapes for the before patch
- const before: Record> = {};
-
- // The shapes for the after patch
- const after: Record> = {};
-
- // Find the shapes that we want to rotate.
- // We don't rotate groups: we rotate their children instead.
- const shapesToRotate = ids
- .flatMap(id => {
- const shape = app.getShape(id);
- return shape.children
- ? shape.children.map(childId => app.getShape(childId))
- : shape;
- })
- .filter(shape => !shape.isLocked);
-
- // Find the common center to all shapes
- // This is the point that we'll rotate around
- const origin = Utils.getBoundsCenter(
- Utils.getCommonBounds(
- shapesToRotate.map(shape => TLDR.get_bounds(shape))
- )
- );
-
- // Find the rotate mutations for each shape
- shapesToRotate.forEach(shape => {
- const change = TLDR.get_rotated_shape_mutation(
- shape,
- TLDR.get_center(shape),
- origin,
- delta
- );
- if (!change) return;
- before[shape.id] = TLDR.get_before_shape(shape, change);
- after[shape.id] = change;
- });
-
- return {
- id: 'rotate',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: before },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: after },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/set-shapes-lock-status.ts b/libs/components/board-commands/src/set-shapes-lock-status.ts
deleted file mode 100644
index 05ea0bd5e..000000000
--- a/libs/components/board-commands/src/set-shapes-lock-status.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function setShapesLockStatus(
- app: TldrawApp,
- ids: string[],
- isLocked: boolean
-): TldrawCommand {
- const { currentPageId, selectedIds } = app;
-
- const initialShapes = ids.map(id => app.getShape(id));
-
- const before: Record> = {};
- const after: Record> = {};
-
- initialShapes.forEach(shape => {
- before[shape.id] = {
- isLocked: shape.isLocked,
- };
- after[shape.id] = {
- isLocked,
- };
- });
-
- return {
- id: 'set_shapes_lock_status',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: before,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: after,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/set-shapes-props.ts b/libs/components/board-commands/src/set-shapes-props.ts
deleted file mode 100644
index a8b7582c4..000000000
--- a/libs/components/board-commands/src/set-shapes-props.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function setShapesProps(
- app: TldrawApp,
- ids: string[],
- partial: Partial
-): TldrawCommand {
- const { currentPageId, selectedIds } = app;
-
- const initialShapes = ids
- .map(id => app.getShape(id))
- .filter(shape => (partial['isLocked'] ? true : !shape.isLocked));
-
- const before: Record> = {};
- const after: Record> = {};
-
- const keys = Object.keys(partial) as (keyof T)[];
-
- initialShapes.forEach(shape => {
- before[shape.id] = Object.fromEntries(
- keys.map(key => [key, shape[key]])
- );
- after[shape.id] = partial;
- });
-
- return {
- id: 'set_props',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: before,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: after,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/shared/remove-shapes-from-page.ts b/libs/components/board-commands/src/shared/remove-shapes-from-page.ts
deleted file mode 100644
index 557d653e1..000000000
--- a/libs/components/board-commands/src/shared/remove-shapes-from-page.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- ArrowShape,
- GroupShape,
- PagePartial,
- TDSnapshot,
-} from '@toeverything/components/board-types';
-
-export function removeShapesFromPage(
- data: TDSnapshot,
- ids: string[],
- pageId: string
-) {
- const before: PagePartial = {
- shapes: {},
- bindings: {},
- };
-
- const after: PagePartial = {
- shapes: {},
- bindings: {},
- };
-
- const parentsToUpdate: GroupShape[] = [];
- const deletedIds = new Set();
- const assetsToRemove = new Set();
-
- // These are the shapes we're definitely going to delete
-
- ids.filter(id => !TLDR.get_shape(data, id, pageId).isLocked).forEach(id => {
- deletedIds.add(id);
- const shape = TLDR.get_shape(data, id, pageId);
- before.shapes[id] = shape;
- after.shapes[id] = undefined;
-
- // Also delete the shape's children
-
- if (shape.children !== undefined) {
- shape.children.forEach(childId => {
- deletedIds.add(childId);
- const child = TLDR.get_shape(data, childId, pageId);
- before.shapes[childId] = child;
- after.shapes[childId] = undefined;
- });
- }
-
- if (shape.parentId !== pageId) {
- parentsToUpdate.push(TLDR.get_shape(data, shape.parentId, pageId));
- }
-
- if (shape.assetId) {
- assetsToRemove.add(shape.assetId);
- }
- });
-
- parentsToUpdate.forEach(parent => {
- if (ids.includes(parent.id)) return;
- deletedIds.add(parent.id);
- before.shapes[parent.id] = { children: parent.children };
- after.shapes[parent.id] = {
- children: parent.children.filter(id => !ids.includes(id)),
- };
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- if (after.shapes[parent.id]?.children!.length === 0) {
- after.shapes[parent.id] = undefined;
- before.shapes[parent.id] = TLDR.get_shape(data, parent.id, pageId);
- }
- });
-
- // Recursively check for empty parents?
-
- const page = TLDR.get_page(data, pageId);
-
- // We also need to delete bindings that reference the deleted shapes
- Object.values(page.bindings)
- .filter(
- binding =>
- deletedIds.has(binding.fromId) || deletedIds.has(binding.toId)
- )
- .forEach(binding => {
- for (const id of [binding.toId, binding.fromId]) {
- // If the binding references a deleted shape...
- if (after.shapes[id] === undefined) {
- // Delete this binding
- before.bindings[binding.id] = binding;
- after.bindings[binding.id] = undefined;
-
- // Let's also look each the bound shape...
- const shape = page.shapes[id];
-
- // If the bound shape has a handle that references the deleted binding...
- if (shape && shape.handles) {
- Object.values(shape.handles)
- .filter(handle => handle.bindingId === binding.id)
- .forEach(handle => {
- // Save the binding reference in the before patch
- before.shapes[id] = {
- ...before.shapes[id],
- handles: {
- ...before.shapes[id]?.handles,
- [handle.id]: {
- ...before.shapes[id]?.handles?.[
- handle.id as keyof ArrowShape['handles']
- ],
- bindingId: binding.id,
- },
- },
- };
-
- // Unless we're currently deleting the shape, remove the
- // binding reference from the after patch
- if (!deletedIds.has(id)) {
- after.shapes[id] = {
- ...after.shapes[id],
- handles: {
- ...after.shapes[id]?.handles,
- [handle.id]: {
- ...after.shapes[id]?.handles?.[
- handle.id as keyof ArrowShape['handles']
- ],
- bindingId: undefined,
- },
- },
- };
- }
- });
- }
- }
- }
- });
-
- // If any other shapes are using the deleted assets, don't remove them
- Object.values(data.document.pages)
- .flatMap(page => Object.values(page.shapes))
- .forEach(shape => {
- if (
- 'assetId' in shape &&
- shape.assetId &&
- !deletedIds.has(shape.id)
- ) {
- assetsToRemove.delete(shape.assetId);
- }
- });
-
- return { before, after, assetsToRemove: Array.from(assetsToRemove) };
-}
diff --git a/libs/components/board-commands/src/stretch-shapes.ts b/libs/components/board-commands/src/stretch-shapes.ts
deleted file mode 100644
index e68bbee6e..000000000
--- a/libs/components/board-commands/src/stretch-shapes.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { TLBoundsCorner, Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type { TldrawCommand } from '@toeverything/components/board-types';
-import { StretchType, TDShapeType } from '@toeverything/components/board-types';
-
-export function stretchShapes(
- app: TldrawApp,
- ids: string[],
- type: StretchType
-): TldrawCommand {
- const { currentPageId, selectedIds } = app;
-
- const initialShapes = ids.map(id => app.getShape(id));
-
- const boundsForShapes = initialShapes.map(shape => TLDR.get_bounds(shape));
-
- const commonBounds = Utils.getCommonBounds(boundsForShapes);
-
- const idsToMutate = ids
- .flatMap(id => {
- const shape = app.getShape(id);
- return shape.children ? shape.children : shape.id;
- })
- .filter(id => !app.getShape(id).isLocked);
-
- const { before, after } = TLDR.mutate_shapes(
- app.state,
- idsToMutate,
- shape => {
- const bounds = TLDR.get_bounds(shape);
-
- switch (type) {
- case StretchType.Horizontal: {
- const newBounds = {
- ...bounds,
- minX: commonBounds.minX,
- maxX: commonBounds.maxX,
- width: commonBounds.width,
- };
-
- return TLDR.get_shape_util(shape).transformSingle(
- shape,
- newBounds,
- {
- type: TLBoundsCorner.TopLeft,
- scaleX: newBounds.width / bounds.width,
- scaleY: 1,
- initialShape: shape,
- transformOrigin: [0.5, 0.5],
- }
- );
- }
- case StretchType.Vertical: {
- const newBounds = {
- ...bounds,
- minY: commonBounds.minY,
- maxY: commonBounds.maxY,
- height: commonBounds.height,
- };
-
- return TLDR.get_shape_util(shape).transformSingle(
- shape,
- newBounds,
- {
- type: TLBoundsCorner.TopLeft,
- scaleX: 1,
- scaleY: newBounds.height / bounds.height,
- initialShape: shape,
- transformOrigin: [0.5, 0.5],
- }
- );
- }
- }
- },
- currentPageId
- );
-
- initialShapes.forEach(shape => {
- if (shape.type === TDShapeType.Group) {
- delete before[shape.id];
- delete after[shape.id];
- }
- });
-
- return {
- id: 'stretch',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: before },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: after },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/style-shapes.ts b/libs/components/board-commands/src/style-shapes.ts
deleted file mode 100644
index bb3e248a5..000000000
--- a/libs/components/board-commands/src/style-shapes.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- Patch,
- ShapeStyles,
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function styleShapes(
- app: TldrawApp,
- ids: string[],
- changes: Partial
-): TldrawCommand {
- const { currentPageId, selectedIds } = app;
-
- const shapeIdsToMutate = ids
- .flatMap(id => TLDR.get_document_branch(app.state, id, currentPageId))
- .filter(id => !app.getShape(id).isLocked);
-
- const beforeShapes: Record> = {};
- const afterShapes: Record> = {};
-
- shapeIdsToMutate
- .map(id => app.getShape(id))
- .filter(shape => !shape.isLocked)
- .forEach(shape => {
- beforeShapes[shape.id] = {
- style: {
- ...Object.fromEntries(
- Object.keys(changes).map(key => [
- key,
- shape.style[key as keyof typeof shape.style],
- ])
- ),
- },
- };
-
- afterShapes[shape.id] = {
- style: changes,
- };
-
- // if (shape.type === TDShapeType.Text) {
- // beforeShapes[shape.id].point = shape.point;
- // afterShapes[shape.id].point = Vec.toFixed(
- // Vec.add(
- // shape.point,
- // Vec.sub(
- // app.getShapeUtil(shape).getCenter(shape),
- // app.getShapeUtil(shape).getCenter({
- // ...shape,
- // style: { ...shape.style, ...changes }
- // } as TextShape)
- // )
- // )
- // );
- // }
- });
-
- return {
- id: 'style',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: selectedIds,
- },
- },
- },
- appState: {
- currentStyle: { ...app.appState.currentStyle },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- appState: {
- currentStyle: changes,
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/toggle-shapes-decoration.ts b/libs/components/board-commands/src/toggle-shapes-decoration.ts
deleted file mode 100644
index 443cb3b8a..000000000
--- a/libs/components/board-commands/src/toggle-shapes-decoration.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- ArrowShape,
- Patch,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-import { Decoration } from '@toeverything/components/board-types';
-
-export function toggleShapesDecoration(
- app: TldrawApp,
- ids: string[],
- decorationId: 'start' | 'end'
-): TldrawCommand {
- const { currentPageId, selectedIds } = app;
-
- const beforeShapes: Record> = Object.fromEntries(
- ids.map(id => [
- id,
- {
- decorations: {
- [decorationId]:
- app.getShape(id).decorations?.[
- decorationId
- ],
- },
- },
- ])
- );
-
- const afterShapes: Record> = Object.fromEntries(
- ids
- .filter(id => !app.getShape(id).isLocked)
- .map(id => [
- id,
- {
- decorations: {
- [decorationId]: app.getShape(id)
- .decorations?.[decorationId]
- ? undefined
- : Decoration.Arrow,
- },
- },
- ])
- );
-
- return {
- id: 'toggle_decorations',
- before: {
- document: {
- pages: {
- [currentPageId]: { shapes: beforeShapes },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: { shapes: afterShapes },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/toggle-shapes-prop.ts b/libs/components/board-commands/src/toggle-shapes-prop.ts
deleted file mode 100644
index f52c55986..000000000
--- a/libs/components/board-commands/src/toggle-shapes-prop.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function toggleShapesProp(
- app: TldrawApp,
- ids: string[],
- prop: keyof TDShape
-): TldrawCommand {
- const { currentPageId } = app;
-
- const initialShapes = ids
- .map(id => app.getShape(id))
- .filter(shape => (prop === 'isLocked' ? true : !shape.isLocked));
-
- const isAllToggled = initialShapes.every(shape => shape[prop]);
-
- const before: Record> = {};
- const after: Record> = {};
-
- initialShapes.forEach(shape => {
- before[shape.id] = { [prop]: shape[prop] };
- after[shape.id] = { [prop]: !isAllToggled };
- });
-
- return {
- id: 'toggle',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: before,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: after,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/translate-shapes.ts b/libs/components/board-commands/src/translate-shapes.ts
deleted file mode 100644
index 4da1a2a70..000000000
--- a/libs/components/board-commands/src/translate-shapes.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- PagePartial,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function translateShapes(
- app: TldrawApp,
- ids: string[],
- delta: number[]
-): TldrawCommand {
- const { currentPageId, selectedIds } = app;
-
- // Clear session cache
- app.rotationInfo.selectedIds = [...selectedIds];
-
- const before: PagePartial = {
- shapes: {},
- bindings: {},
- };
-
- const after: PagePartial = {
- shapes: {},
- bindings: {},
- };
-
- const idsToMutate = ids
- .flatMap(id => {
- const shape = app.getShape(id);
- return shape.children ? shape.children : shape.id;
- })
- .filter(id => !app.getShape(id).isLocked);
-
- const change = TLDR.mutate_shapes(
- app.state,
- idsToMutate,
- shape => ({
- point: Vec.toFixed(Vec.add(shape.point, delta)),
- }),
- currentPageId
- );
-
- before.shapes = change.before;
- after.shapes = change.after;
-
- // Delete bindings from nudged shapes, unless both bound and bound-to shapes are selected
- const bindingsToDelete = TLDR.get_bindings(app.state, currentPageId).filter(
- binding => ids.includes(binding.fromId) && !ids.includes(binding.toId)
- );
-
- bindingsToDelete.forEach(binding => {
- before.bindings[binding.id] = binding;
- after.bindings[binding.id] = undefined;
-
- for (const id of [binding.toId, binding.fromId]) {
- // Let's also look at the bound shape...
- const shape = app.getShape(id);
-
- if (!shape.handles) continue;
-
- // If the bound shape has a handle that references the deleted binding, delete that reference
-
- Object.values(shape.handles)
- .filter(handle => handle.bindingId === binding.id)
- .forEach(handle => {
- before.shapes[id] = {
- ...before.shapes[id],
- handles: {
- ...before.shapes[id]?.handles,
- [handle.id]: { bindingId: binding.id },
- },
- };
- after.shapes[id] = {
- ...after.shapes[id],
- handles: {
- ...after.shapes[id]?.handles,
- [handle.id]: { bindingId: undefined },
- },
- };
- });
- }
- });
-
- return {
- id: 'translate',
- before: {
- document: {
- pages: {
- [currentPageId]: before,
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: after,
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: ids,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/ungroup-shapes.ts b/libs/components/board-commands/src/ungroup-shapes.ts
deleted file mode 100644
index 160c46819..000000000
--- a/libs/components/board-commands/src/ungroup-shapes.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- GroupShape,
- Patch,
- TDBinding,
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function ungroupShapes(
- app: TldrawApp,
- selectedIds: string[],
- groupShapes: GroupShape[],
- pageId: string
-): TldrawCommand | undefined {
- const { bindings } = app;
-
- const beforeShapes: Record> = {};
- const afterShapes: Record> = {};
-
- const beforeBindings: Record> = {};
- const afterBindings: Record> = {};
-
- const beforeSelectedIds = selectedIds;
- const afterSelectedIds = selectedIds.filter(
- id => !groupShapes.find(shape => shape.id === id)
- );
-
- // The group shape
- groupShapes
- .filter(shape => !shape.isLocked)
- .forEach(groupShape => {
- const shapesToReparent: TDShape[] = [];
- const deletedGroupIds: string[] = [];
-
- // Remove the group shape in the next state
- beforeShapes[groupShape.id] = groupShape;
- afterShapes[groupShape.id] = undefined;
-
- // Select its children in the next state
- groupShape.children.forEach(id => {
- afterSelectedIds.push(id);
- const shape = app.getShape(id, pageId);
- shapesToReparent.push(shape);
- });
-
- // We'll start placing the shapes at this childIndex
- const startingChildIndex = groupShape.childIndex;
-
- // And we'll need to fit them under this child index
- const endingChildIndex = TLDR.get_child_index_above(
- app.state,
- groupShape.id,
- pageId
- );
-
- const step =
- (endingChildIndex - startingChildIndex) /
- shapesToReparent.length;
-
- // An array of shapes in order by their child index
- const sortedShapes = shapesToReparent.sort(
- (a, b) => a.childIndex - b.childIndex
- );
-
- // Reparent shapes to the page
- sortedShapes.forEach((shape, index) => {
- beforeShapes[shape.id] = {
- parentId: shape.parentId,
- childIndex: shape.childIndex,
- };
-
- afterShapes[shape.id] = {
- parentId: pageId,
- childIndex: startingChildIndex + step * index,
- };
- });
-
- // We also need to delete bindings that reference the deleted shapes
- bindings
- .filter(
- binding =>
- binding.toId === groupShape.id ||
- binding.fromId === groupShape.id
- )
- .forEach(binding => {
- for (const id of [binding.toId, binding.fromId]) {
- // If the binding references the deleted group...
- if (afterShapes[id] === undefined) {
- // Delete the binding
- beforeBindings[binding.id] = binding;
- afterBindings[binding.id] = undefined;
-
- // Let's also look each the bound shape...
- const shape = app.getShape(id, pageId);
-
- // If the bound shape has a handle that references the deleted binding...
- if (shape.handles) {
- Object.values(shape.handles)
- .filter(
- handle =>
- handle.bindingId === binding.id
- )
- .forEach(handle => {
- // Save the binding reference in the before patch
- beforeShapes[id] = {
- ...beforeShapes[id],
- handles: {
- ...beforeShapes[id]?.handles,
- [handle.id]: {
- bindingId: binding.id,
- },
- },
- };
-
- // Unless we're currently deleting the shape, remove the
- // binding reference from the after patch
- if (!deletedGroupIds.includes(id)) {
- afterShapes[id] = {
- ...afterShapes[id],
- handles: {
- ...afterShapes[id]?.handles,
- [handle.id]: {
- bindingId: undefined,
- },
- },
- };
- }
- });
- }
- }
- }
- });
- });
-
- return {
- id: 'ungroup',
- before: {
- document: {
- pages: {
- [pageId]: {
- shapes: beforeShapes,
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: beforeSelectedIds,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [pageId]: {
- shapes: afterShapes,
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: afterSelectedIds,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/src/update-shapes.ts b/libs/components/board-commands/src/update-shapes.ts
deleted file mode 100644
index 304190634..000000000
--- a/libs/components/board-commands/src/update-shapes.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type {
- TDShape,
- TldrawCommand,
-} from '@toeverything/components/board-types';
-
-export function updateShapes(
- app: TldrawApp,
- updates: ({ id: string } & Partial)[],
- pageId: string
-): TldrawCommand {
- const ids = updates.map(update => update.id);
-
- const change = TLDR.mutate_shapes(
- app.state,
- ids.filter(id => !app.getShape(id, pageId).isLocked),
- (shape, i) => updates[i],
- pageId
- );
-
- return {
- id: 'update',
- before: {
- document: {
- pages: {
- [pageId]: {
- shapes: change.before,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [pageId]: {
- shapes: change.after,
- },
- },
- },
- },
- };
-}
diff --git a/libs/components/board-commands/tsconfig.json b/libs/components/board-commands/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/board-commands/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/board-commands/tsconfig.lib.json b/libs/components/board-commands/tsconfig.lib.json
deleted file mode 100644
index 6a4378740..000000000
--- a/libs/components/board-commands/tsconfig.lib.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "jest.config.ts",
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/board-commands/tsconfig.spec.json b/libs/components/board-commands/tsconfig.spec.json
deleted file mode 100644
index 24b3ac659..000000000
--- a/libs/components/board-commands/tsconfig.spec.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "jest.config.ts",
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/board-draw/.babelrc b/libs/components/board-draw/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/board-draw/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/board-draw/.eslintrc.json b/libs/components/board-draw/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/board-draw/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/board-draw/README.md b/libs/components/board-draw/README.md
deleted file mode 100644
index 946854736..000000000
--- a/libs/components/board-draw/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-board-draw
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test components-board-draw` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/board-draw/jest.config.ts b/libs/components/board-draw/jest.config.ts
deleted file mode 100644
index c689c4139..000000000
--- a/libs/components/board-draw/jest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'components-board-draw',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/board-draw',
-};
diff --git a/libs/components/board-draw/package.json b/libs/components/board-draw/package.json
deleted file mode 100644
index d8cd3a1e9..000000000
--- a/libs/components/board-draw/package.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "name": "@toeverything/components/board-draw",
- "version": "0.0.1",
- "license": "MIT",
- "dependencies": {
- "@mui/icons-material": "^5.8.0",
- "@tldraw/core": "^1.12.0",
- "@tldraw/intersect": "^1.7.1",
- "@tldraw/vec": "^1.7.0",
- "mobx": "^6.6.0",
- "react-error-boundary": "^3.1.4",
- "react-hotkeys-hook": "^3.4.6"
- }
-}
diff --git a/libs/components/board-draw/project.json b/libs/components/board-draw/project.json
deleted file mode 100644
index 7e7f851fa..000000000
--- a/libs/components/board-draw/project.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/components/board-draw/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/board-draw",
- "tsConfig": "libs/components/board-draw/tsconfig.lib.json",
- "project": "libs/components/board-draw/package.json",
- "entryFile": "libs/components/board-draw/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/board-draw/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/board-draw/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/board-draw"],
- "options": {
- "jestConfig": "libs/components/board-draw/jest.config.ts",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/board-draw/src/TlDraw.tsx b/libs/components/board-draw/src/TlDraw.tsx
deleted file mode 100644
index a1444b76c..000000000
--- a/libs/components/board-draw/src/TlDraw.tsx
+++ /dev/null
@@ -1,565 +0,0 @@
-/* eslint-disable max-lines */
-import { Renderer } from '@tldraw/core';
-import { shapeUtils } from '@toeverything/components/board-shapes';
-import {
- TLDR,
- TldrawApp,
- TldrawAppCtorProps,
-} from '@toeverything/components/board-state';
-import {
- GRID_SIZE,
- TDDocument,
- TDMeta,
- TDStatus,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-// import { FocusButton } from '~components/FocusButton';
-import { usePageClientWidth } from '@toeverything/datasource/state';
-import {
- memo,
- useEffect,
- useLayoutEffect,
- useMemo,
- useRef,
- useState,
- type RefObject,
-} from 'react';
-import { ErrorBoundary } from 'react-error-boundary';
-import { CommandPanel } from './components/command-panel';
-// import { TopPanel } from '~components/TopPanel';
-import { ContextMenu } from './components/context-menu';
-import { ErrorFallback } from './components/error-fallback';
-import { Loading } from './components/loading';
-import { ToolsPanel } from './components/tools-panel';
-import { ZoomBar } from './components/zoom-bar';
-import {
- TldrawContext,
- useKeyboardShortcuts,
- useStylesheet,
- useTldrawApp,
-} from './hooks';
-
-export interface TldrawProps extends TldrawAppCtorProps {
- /**
- * (optional) If provided, the component will load / persist state under this key.
- */
- id?: string;
-
- /**
- * (optional) The document to load or update from.
- */
- document?: TDDocument;
-
- /**
- * (optional) The current page id.
- */
- currentPageId?: string;
-
- /**
- * (optional) Whether the editor should immediately receive focus. Defaults to true.
- */
- autofocus?: boolean;
-
- /**
- * (optional) Whether to show the menu UI.
- */
- showMenu?: boolean;
-
- /**
- * (optional) Whether to show the multiplayer menu.
- */
- showMultiplayerMenu?: boolean;
- /**
- * (optional) Whether to show the pages UI.
- */
- showPages?: boolean;
-
- /**
- * (optional) Whether to show the styles UI.
- */
- showStyles?: boolean;
-
- /**
- * (optional) Whether to show the zoom UI.
- */
- showZoom?: boolean;
-
- /**
- * (optional) Whether to show the tools UI.
- */
- showTools?: boolean;
-
- /**
- * (optional) Whether to show a sponsor link for Tldraw.
- */
- showSponsorLink?: boolean;
-
- /**
- * (optional) Whether to show the UI.
- */
- showUI?: boolean;
-
- /**
- * (optional) Whether to the document should be read only.
- */
- readOnly?: boolean;
-
- /**
- * (optional) Whether to to show the app's dark mode UI.
- */
- darkMode?: boolean;
-
- /**
- * (optional) If provided, image/video componnets will be disabled.
- *
- * Warning: Keeping this enabled for multiplayer applications without provifing a storage
- * bucket based solution will cause massive base64 string to be written to the liveblocks room.
- */
- disableAssets?: boolean;
-}
-
-export function Tldraw({
- id,
- document,
- currentPageId,
- autofocus = true,
- showMenu = true,
- showMultiplayerMenu = true,
- showPages = true,
- showTools = true,
- showZoom = true,
- showStyles = true,
- // eslint-disable-next-line @typescript-eslint/naming-convention
- showUI = true,
- readOnly = false,
- disableAssets = false,
- darkMode = false,
- showSponsorLink,
- callbacks,
- commands,
- getSession,
- tools,
-}: TldrawProps) {
- const [sId, setSid] = useState(id);
- const { pageClientWidth } = usePageClientWidth();
- // page padding left and right total 300px
- const editorShapeInitSize = pageClientWidth - 300;
-
- // Create a new app when the component mounts.
- const [app, setApp] = useState(() => {
- const app = new TldrawApp({
- id,
- callbacks,
- commands,
- getSession,
- editorShapeInitSize,
- tools,
- });
- return app;
- });
-
- // Create a new app if the `id` prop changes.
- useLayoutEffect(() => {
- if (id === sId) return;
- const newApp = new TldrawApp({
- id,
- callbacks,
- commands,
- getSession,
- tools,
- });
-
- setSid(id);
-
- setApp(newApp);
- }, [sId, id]);
-
- // Update the document if the `document` prop changes but the ids,
- // are the same, or else load a new document if the ids are different.
- useEffect(() => {
- if (!document) return;
-
- if (document.id === app.document.id) {
- app.updateDocument(document);
- } else {
- app.loadDocument(document);
- }
- }, [document, app]);
-
- // Disable assets when the `disableAssets` prop changes.
- useEffect(() => {
- app.setDisableAssets(disableAssets);
- }, [app, disableAssets]);
-
- // Change the page when the `currentPageId` prop changes.
- useEffect(() => {
- if (!currentPageId) return;
- app.changePage(currentPageId);
- }, [currentPageId, app]);
-
- // Toggle the app's readOnly mode when the `readOnly` prop changes.
- useEffect(() => {
- app.readOnly = readOnly;
- }, [app, readOnly]);
-
- // Toggle the app's darkMode when the `darkMode` prop changes.
- useEffect(() => {
- if (darkMode !== app.settings.isDarkMode) {
- app.toggleDarkMode();
- }
- }, [app, darkMode]);
-
- // Update the app's callbacks when any callback changes.
- useEffect(() => {
- app.callbacks = callbacks || {};
- }, [app, callbacks]);
-
- useLayoutEffect(() => {
- if (typeof window === 'undefined') return;
- if (!window.document?.fonts) return;
-
- function refreshBoundingBoxes() {
- app.refreshBoundingBoxes();
- }
- window.document.fonts.addEventListener(
- 'loadingdone',
- refreshBoundingBoxes
- );
- return () => {
- window.document.fonts.removeEventListener(
- 'loadingdone',
- refreshBoundingBoxes
- );
- };
- }, [app]);
-
- // Use the `key` to ensure that new selector hooks are made when the id changes
- return (
-
-
-
- );
-}
-
-interface InnerTldrawProps {
- id?: string;
- autofocus: boolean;
- readOnly: boolean;
- showPages: boolean;
- showMenu: boolean;
- showMultiplayerMenu: boolean;
- showZoom: boolean;
- showStyles: boolean;
- showUI: boolean;
- showTools: boolean;
- showSponsorLink?: boolean;
-}
-
-const InnerTldraw = memo(function InnerTldraw({
- id,
- autofocus,
- showPages,
- showMenu,
- showMultiplayerMenu,
- showZoom,
- showStyles,
- showTools,
- showSponsorLink,
- readOnly,
- // eslint-disable-next-line @typescript-eslint/naming-convention
- showUI,
-}: InnerTldrawProps) {
- const app = useTldrawApp();
-
- const rWrapper = useRef(null);
-
- const state = app.useStore();
-
- const { document, settings, appState, room } = state;
- const isSelecting = state.appState.activeTool === 'select';
-
- const page = document.pages[appState.currentPageId];
- const pageState = document.pageStates[page.id];
- const assets = document.assets;
- const { selectedIds } = pageState;
- const isHideBoundsShape =
- selectedIds.length === 1 &&
- page.shapes[selectedIds[0]] &&
- TLDR.get_shape_util(page.shapes[selectedIds[0]].type).hideBounds;
-
- const isHideResizeHandlesShape =
- selectedIds.length === 1 &&
- page.shapes[selectedIds[0]] &&
- TLDR.get_shape_util(page.shapes[selectedIds[0]].type).hideResizeHandles;
-
- // Custom rendering meta, with dark mode for shapes
- const meta: TDMeta = useMemo(() => {
- return { isDarkMode: settings.isDarkMode, app };
- }, [settings.isDarkMode, app]);
-
- const showDashedBrush = settings.isCadSelectMode
- ? !appState.selectByContain
- : appState.selectByContain;
-
- // Custom theme, based on darkmode
- const theme = useMemo(() => {
- const { selectByContain } = appState;
- const { isDarkMode, isCadSelectMode } = settings;
-
- if (isDarkMode) {
- const brushBase = isCadSelectMode
- ? selectByContain
- ? '69, 155, 255'
- : '105, 209, 73'
- : '180, 180, 180';
- return {
- brushFill: `rgba(${brushBase}, ${
- isCadSelectMode ? 0.08 : 0.05
- })`,
- brushStroke: `rgba(${brushBase}, ${
- isCadSelectMode ? 0.5 : 0.25
- })`,
- brushDashStroke: `rgba(${brushBase}, .6)`,
- selected: 'rgba(38, 150, 255, 1.000)',
- selectFill: 'rgba(38, 150, 255, 0.05)',
- background: '#212529',
- foreground: '#49555f',
- };
- }
-
- const brushBase = isCadSelectMode
- ? selectByContain
- ? '0, 89, 242'
- : '51, 163, 23'
- : '0,0,0';
-
- return {
- brushFill: `rgba(${brushBase}, ${isCadSelectMode ? 0.08 : 0.05})`,
- brushStroke: `rgba(${brushBase}, ${isCadSelectMode ? 0.4 : 0.25})`,
- brushDashStroke: `rgba(${brushBase}, .6)`,
- background: '#fff',
- };
- }, [
- settings.isDarkMode,
- settings.isCadSelectMode,
- appState.selectByContain,
- ]);
-
- const isInSession = app.session !== undefined;
-
- // Hide bounds when not using the select tool, or when the only selected shape has handles
- const hideBounds =
- (isInSession && app.session?.constructor.name === 'BrushSession') ||
- !isSelecting ||
- isHideBoundsShape ||
- !!pageState.editingId;
-
- // Hide bounds when not using the select tool, or when in session
- const hideHandles = isInSession || !isSelecting;
-
- // Hide indicators when not using the select tool, or when in session
- const hideIndicators =
- (isInSession && state.appState.status !== TDStatus.Brushing) ||
- !isSelecting;
-
- const hideCloneHandles =
- isInSession ||
- !isSelecting ||
- !settings.showCloneHandles ||
- pageState.camera.zoom < 0.2;
-
- return (
-
-
-
-
-
- }
- hideBounds={hideBounds}
- hideHandles={hideHandles}
- hideResizeHandles={isHideResizeHandlesShape}
- hideIndicators={hideIndicators}
- hideBindingHandles={!settings.showBindingHandles}
- hideCloneHandles={hideCloneHandles}
- hideRotateHandles={!settings.showRotateHandles}
- hideGrid={!settings.showGrid}
- showDashedBrush={showDashedBrush}
- performanceMode={app.session?.performanceMode}
- onPinchStart={app.onPinchStart}
- onPinchEnd={app.onPinchEnd}
- onPinch={app.onPinch}
- onPan={app.onPan}
- onZoom={app.onZoom}
- onPointerDown={app.onPointerDown}
- onPointerMove={app.onPointerMove}
- onPointerUp={app.onPointerUp}
- onPointCanvas={app.onPointCanvas}
- onDoubleClickCanvas={app.onDoubleClickCanvas}
- onRightPointCanvas={app.onRightPointCanvas}
- onDragCanvas={app.onDragCanvas}
- onReleaseCanvas={app.onReleaseCanvas}
- onPointShape={app.onPointShape}
- onDoubleClickShape={app.onDoubleClickShape}
- onRightPointShape={app.onRightPointShape}
- onDragShape={app.onDragShape}
- onHoverShape={app.onHoverShape}
- onUnhoverShape={app.onUnhoverShape}
- onReleaseShape={app.onReleaseShape}
- onPointBounds={app.onPointBounds}
- onDoubleClickBounds={app.onDoubleClickBounds}
- onRightPointBounds={app.onRightPointBounds}
- onDragBounds={app.onDragBounds}
- onHoverBounds={app.onHoverBounds}
- onUnhoverBounds={app.onUnhoverBounds}
- onReleaseBounds={app.onReleaseBounds}
- onPointBoundsHandle={app.onPointBoundsHandle}
- onDoubleClickBoundsHandle={
- app.onDoubleClickBoundsHandle
- }
- onRightPointBoundsHandle={app.onRightPointBoundsHandle}
- onDragBoundsHandle={app.onDragBoundsHandle}
- onHoverBoundsHandle={app.onHoverBoundsHandle}
- onUnhoverBoundsHandle={app.onUnhoverBoundsHandle}
- onReleaseBoundsHandle={app.onReleaseBoundsHandle}
- onPointHandle={app.onPointHandle}
- onDoubleClickHandle={app.onDoubleClickHandle}
- onRightPointHandle={app.onRightPointHandle}
- onDragHandle={app.onDragHandle}
- onHoverHandle={app.onHoverHandle}
- onUnhoverHandle={app.onUnhoverHandle}
- onReleaseHandle={app.onReleaseHandle}
- onError={app.onError}
- onRenderCountChange={app.onRenderCountChange}
- onShapeChange={app.onShapeChange}
- onShapeBlur={app.onShapeBlur}
- onShapeClone={app.onShapeClone}
- onBoundsChange={app.updateBounds}
- onKeyDown={app.onKeyDown}
- onKeyUp={app.onKeyUp}
- onDragOver={app.onDragOver}
- onDrop={app.onDrop}
- />
-
-
- {showUI && (
-
- <>
-
- {showTools && !readOnly && }
-
- >
-
- )}
-
-
- );
-});
-
-const OneOff = memo(function OneOff({
- focusableRef,
- autofocus,
-}: {
- autofocus?: boolean;
- focusableRef: RefObject;
-}) {
- useKeyboardShortcuts(focusableRef);
- useStylesheet();
-
- useEffect(() => {
- if (autofocus) {
- focusableRef.current?.focus();
- }
- }, [autofocus]);
-
- return null;
-});
-
-const StyledLayout = styled('div')<{ penColor: string; panning: boolean }>(
- ({ theme, panning, penColor }) => {
- return {
- position: 'relative',
- height: '100%',
- width: '100vw',
- minHeight: 0,
- minWidth: 0,
- maxHeight: '100%',
- maxWidth: '100%',
- overflow: 'hidden',
- boxSizing: 'border-box',
- outline: 'none',
- cursor: panning ? 'grab' : 'unset',
-
- '& .tl-container': {
- position: 'absolute',
- top: 0,
- left: 0,
- height: '100%',
- width: '100%',
- zIndex: 1,
- '.tl-erase-line': {
- fill: penColor,
- },
- },
-
- '& input, textarea, button, select, label, button': {
- webkitTouchCallout: 'none',
- webkitUserSelect: 'none',
- WebkitTapHighlightColor: 'transparent',
- tapHighlightColor: 'transparent',
- },
- };
- }
-);
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-const StyledUI = styled('div')({
- position: 'absolute',
- top: 0,
- left: 0,
- height: '100%',
- width: '100%',
- padding: '8px 8px 0 8px',
- display: 'flex',
- alignItems: 'flex-start',
- justifyContent: 'flex-start',
- pointerEvents: 'none',
- zIndex: 2,
- '& > *': {
- pointerEvents: 'all',
- },
-});
-
-const StyledSpacer = styled('div')({
- flexGrow: 2,
-});
diff --git a/libs/components/board-draw/src/components/align-panel/index.tsx b/libs/components/board-draw/src/components/align-panel/index.tsx
deleted file mode 100644
index ddc43b125..000000000
--- a/libs/components/board-draw/src/components/align-panel/index.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { styled, Tooltip } from '@toeverything/components/ui';
-import { AlignType } from '../command-panel/AlignOperation';
-
-interface AlignObject {
- name?: string;
- /**
- * color: none means no color
- */
- title: string;
- icon?: JSX.Element;
-}
-/**
- * ColorValue : none means no color
- */
-interface AlignProps {
- alignOptions: AlignObject[];
- selected?: string;
- onSelect?: (alginType: AlignType) => void;
-}
-
-export const AlignPanel = ({
- alignOptions,
- selected,
- onSelect,
-}: AlignProps) => {
- return (
-
- {alignOptions.map(alignOption => {
- const option = alignOption.name as AlignType;
- // const selected = color;
- return (
-
- {
- onSelect?.(option);
- }}
- >
- {alignOption.icon}
-
-
- );
- })}
-
- );
-};
-
-const Container = styled('div')({
- width: '170px',
- display: 'flex',
- flexWrap: 'wrap',
-});
-
-const SelectableContainer = styled('div')<{ selected?: boolean }>(
- ({ selected, theme }) => ({
- color: theme.affine.palette.icons,
- borderRadius: '5px',
- overflow: 'hidden',
- margin: '5px',
- width: '24px',
- height: '24px',
- cursor: 'pointer',
- boxSizing: 'border-box',
- '&:hover': {
- backgroundColor: theme.affine.palette.hover,
- },
- })
-);
diff --git a/libs/components/board-draw/src/components/command-panel/AlignOperation.tsx b/libs/components/board-draw/src/components/command-panel/AlignOperation.tsx
deleted file mode 100644
index d8f5b307e..000000000
--- a/libs/components/board-draw/src/components/command-panel/AlignOperation.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { DistributeType, TDShape } from '@toeverything/components/board-types';
-import {
- AlignIcon,
- ShapesAlignBottomIcon,
- ShapesAlignHorizontalCenterIcon,
- ShapesAlignLeftIcon,
- ShapesAlignRightIcon,
- ShapesAlignTopIcon,
- ShapesAlignVerticalCenterIcon,
- ShapesDistributeHorizontalIcon,
- ShapesDistributeVerticalIcon,
-} from '@toeverything/components/icons';
-import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
-import { AlignPanel } from '../align-panel';
-interface BorderColorConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-export enum AlignType {
- Top = 'top',
- CenterVertical = 'centerVertical',
- Bottom = 'bottom',
- Left = 'left',
- CenterHorizontal = 'centerHorizontal',
- Right = 'right',
-}
-
-let AlignPanelArr = [
- {
- name: 'left',
- title: 'Align left',
- icon: ,
- },
- {
- name: 'centerVertical',
- title: 'Align Center Vertical',
- icon: ,
- },
- {
- name: 'right',
- title: 'Align right',
- icon: ,
- },
- {
- name: 'top',
- title: 'Align top',
- icon: ,
- },
- {
- name: 'bottom',
- title: 'Align bottom',
- icon: ,
- },
-
- {
- name: 'centerHorizontal',
- title: 'Align centerHorizontal',
- icon: (
-
- ),
- },
- {
- name: 'distributeCenterHorizontal',
- title: 'Align distribute center horizontal',
- icon: ,
- },
- {
- name: 'distributeCenterVertical',
- title: 'Align distribute center horizontal',
- icon: ,
- },
-];
-export const AlignOperation = ({ app, shapes }: BorderColorConfigProps) => {
- const setAlign = (alginType: string) => {
- switch (alginType) {
- case 'distributeCenterHorizontal':
- app.distribute(DistributeType.Horizontal);
- break;
- case 'distributeCenterVertical':
- app.distribute(DistributeType.Vertical);
- break;
- default:
- app.align(alginType as AlignType);
- break;
- }
- };
-
- return (
-
- }
- >
-
-
-
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/ArrowTo.tsx b/libs/components/board-draw/src/components/command-panel/ArrowTo.tsx
deleted file mode 100644
index 84ef142ef..000000000
--- a/libs/components/board-draw/src/components/command-panel/ArrowTo.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type {
- ArrowBinding,
- TDShape,
-} from '@toeverything/components/board-types';
-import { ConnectorIcon } from '@toeverything/components/icons';
-import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
-import { useEffect, useState } from 'react';
-import { ListItemContainer, ListItemTitle } from './FontSizeConfig';
-
-interface GroupAndUnGroupProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-export const ArrowTo = ({ app, shapes }: GroupAndUnGroupProps) => {
- const [arrowToArr, setarrowToArr] = useState([]);
- useEffect(() => {
- let allShape = app.shapes;
- let bindings = app.page.bindings;
- let activeShape = shapes[0];
- let toNextShapBindings: ArrowBinding[] = [];
- let bindingId = '';
- if (!activeShape) {
- return;
- }
- Object.keys(bindings).forEach(key => {
- if (bindings[key].toId === activeShape.id) {
- bindingId = bindings[key].fromId;
- }
- });
- Object.keys(bindings).forEach(key => {
- if (bindings[key].fromId === bindingId) {
- toNextShapBindings.push(bindings[key]);
- }
- });
- let ArrowToArr: TDShape[] = [];
- toNextShapBindings.forEach(binding => {
- if (binding.toId !== activeShape.id) {
- allShape.forEach(item => {
- if (item.id === binding.toId) {
- ArrowToArr.push(item);
- }
- });
- }
- });
- setarrowToArr(ArrowToArr);
- return () => {};
- }, [app.page.bindings]);
- const jumpToNextShap = (shape: TDShape) => {
- app.zoomToShapes([shape]);
- };
-
- return (
-
- {arrowToArr.map((arrow: any) => {
- return (
- jumpToNextShap(arrow)}
- >
- {arrow.name}
-
- );
- })}
-
- }
- >
-
-
-
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/BorderColorConfig.tsx b/libs/components/board-draw/src/components/command-panel/BorderColorConfig.tsx
deleted file mode 100644
index 15d6eadc1..000000000
--- a/libs/components/board-draw/src/components/command-panel/BorderColorConfig.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import {
- BorderColorDuotoneIcon,
- BorderColorNoneIcon,
-} from '@toeverything/components/icons';
-import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
-import { countBy, maxBy } from '@toeverything/utils';
-import { Palette } from '../palette';
-import { getShapeIds } from './utils';
-
-interface BorderColorConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-const _colors = [
- 'none',
- '#F1675E',
- '#FF7F22',
- '#FFCB45',
- '#40DF9B',
- '#13D9E3',
- '#3E6FDB',
- '#7352F1',
- '#3A4C5C',
- '#FFFFFF',
-];
-
-const _getIconRenderColor = (shapes: TDShape[]) => {
- const counted = countBy(shapes, shape => shape.style.stroke);
- const max = maxBy(Object.entries(counted), ([c, n]) => n);
- return max[0];
-};
-
-export const BorderColorConfig = ({ app, shapes }: BorderColorConfigProps) => {
- const setBorderColor = (color: string) => {
- app.style({ stroke: color }, getShapeIds(shapes));
- };
-
- const iconColor = _getIconRenderColor(shapes);
-
- return (
-
- }
- >
-
-
- {iconColor === 'none' ? (
-
- ) : (
-
- )}
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx b/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx
deleted file mode 100644
index 031d4240a..000000000
--- a/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import { TLDR, TldrawApp } from '@toeverything/components/board-state';
-import { Divider, Popover, styled } from '@toeverything/components/ui';
-import { Fragment } from 'react';
-import { AlignOperation } from './AlignOperation';
-import { BorderColorConfig } from './BorderColorConfig';
-import { DeleteShapes } from './DeleteOperation';
-import { FillColorConfig } from './FillColorConfig';
-import { FontSizeConfig } from './FontSizeConfig';
-import { FrameFillColorConfig } from './FrameFillColorConfig';
-import { Group, UnGroup } from './GroupOperation';
-import { Lock, Unlock } from './LockOperation';
-import { MoveCoverageConfig } from './MoveCoverage';
-import { StrokeLineStyleConfig } from './stroke-line-style-config';
-import { getAnchor, useConfig } from './utils';
-
-export const CommandPanel = ({ app }: { app: TldrawApp }) => {
- const state = app.useStore();
- const bounds = TLDR.get_selected_bounds(state);
- const camera = app.useStore(
- s => s.document.pageStates[s.appState.currentPageId].camera
- );
- const point = bounds
- ? app.getScreenPoint([bounds.minX, bounds.minY])
- : undefined;
-
- const anchorEl = getAnchor({
- x: point?.[0] || 0,
- y: (point?.[1] || 0) + 40,
- width: bounds?.width ? bounds.width * camera.zoom : 0,
- height: bounds?.height ? bounds.height * camera.zoom : 0,
- });
-
- const config = useConfig(app);
-
- const configNodes = {
- stroke: config.stroke.selectedShapes.length ? (
-
-
-
-
- ) : null,
- fill: config.fill.selectedShapes.length ? (
-
- ) : null,
- frameFill: config.frameFill.selectedShapes.length ? (
-
- ) : null,
- font: config.font.selectedShapes.length ? (
-
- ) : null,
- group: config.group.selectedShapes.length ? (
-
- ) : null,
- ungroup: config.ungroup.selectedShapes.length ? (
-
- ) : null,
- lock: config.lock.selectedShapes.length ? (
-
- ) : null,
- unlock: config.unlock.selectedShapes.length ? (
-
- ) : null,
- delete: (
-
- ),
- moveCoverageConfig: (
-
- ),
- alginOperation: config.group.selectedShapes.length ? (
-
- ) : null,
- // toNextShap: (
- //
- // ),
- };
-
- const nodes = Object.entries(configNodes).filter(([key, node]) => !!node);
-
- return nodes.length ? (
-
- {nodes.map(([key, node], idx, arr) => {
- return (
-
- {node}
- {idx < arr.length - 1 ? (
-
- ) : null}
-
- );
- })}
-
- }
- />
- ) : null;
-};
-
-const PopoverContainer = styled('div')({
- display: 'flex',
-});
diff --git a/libs/components/board-draw/src/components/command-panel/DeleteOperation.tsx b/libs/components/board-draw/src/components/command-panel/DeleteOperation.tsx
deleted file mode 100644
index 74351af17..000000000
--- a/libs/components/board-draw/src/components/command-panel/DeleteOperation.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import { DeleteCashBinIcon } from '@toeverything/components/icons';
-import { IconButton, Tooltip } from '@toeverything/components/ui';
-import { getShapeIds } from './utils';
-
-interface DeleteShapesProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-export const DeleteShapes = ({ app, shapes }: DeleteShapesProps) => {
- const deleteShapes = () => {
- app.delete(getShapeIds(shapes));
- };
- return (
-
-
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/FillColorConfig.tsx b/libs/components/board-draw/src/components/command-panel/FillColorConfig.tsx
deleted file mode 100644
index 909b65eca..000000000
--- a/libs/components/board-draw/src/components/command-panel/FillColorConfig.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import {
- ShapeColorDuotoneIcon,
- ShapeColorNoneIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- Popover,
- Tooltip,
- useTheme,
-} from '@toeverything/components/ui';
-import { countBy, maxBy } from '@toeverything/utils';
-import { Palette } from '../palette';
-import { getShapeIds } from './utils';
-
-interface BorderColorConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-type ColorType = 'none' | string;
-
-const _colors: ColorType[] = [
- 'none',
- '#F1675E',
- '#FF7F22',
- '#FFCB45',
- '#40DF9B',
- '#13D9E3',
- '#3E6FDB',
- '#7352F1',
- '#3A4C5C',
- '#FFFFFF',
-];
-
-const _getIconRenderColor = (shapes: TDShape[]) => {
- const counted = countBy(shapes, shape => shape.style.fill);
- const max = maxBy(Object.entries(counted), ([c, n]) => n);
- return max[0];
-};
-
-export const FillColorConfig = ({ app, shapes }: BorderColorConfigProps) => {
- const theme = useTheme();
- const setFillColor = (color: ColorType) => {
- app.style(
- { fill: color, isFilled: color !== 'none' },
- getShapeIds(shapes)
- );
- };
-
- const iconColor = _getIconRenderColor(shapes);
-
- return (
-
- }
- >
-
-
- {iconColor === 'none' ? (
-
- ) : (
-
- )}
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/FontSizeConfig.tsx b/libs/components/board-draw/src/components/command-panel/FontSizeConfig.tsx
deleted file mode 100644
index 38fbbfae2..000000000
--- a/libs/components/board-draw/src/components/command-panel/FontSizeConfig.tsx
+++ /dev/null
@@ -1,106 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import { FontSizeStyle } from '@toeverything/components/board-types';
-import {
- HeadingOneIcon,
- HeadingThreeIcon,
- HeadingTwoIcon,
- TextFontIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- Popover,
- styled,
- Tooltip,
-} from '@toeverything/components/ui';
-import { countBy, maxBy } from '@toeverything/utils';
-import { getShapeIds } from './utils';
-
-interface FontSizeConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-const _fontSizes = [
- {
- name: 'Heading 1',
- value: FontSizeStyle.h1,
- icon:
,
- },
- {
- name: 'Heading 2',
- value: FontSizeStyle.h2,
- icon:
,
- },
- {
- name: 'Heading 3',
- value: FontSizeStyle.h3,
- icon:
,
- },
- {
- name: 'Text',
- value: FontSizeStyle.body,
- icon:
,
- },
-];
-
-const _getFontSize = (shapes: TDShape[]): FontSizeStyle => {
- const counted = countBy(shapes, shape => shape.style.fill);
- const max = maxBy(Object.entries(counted), ([c, n]) => n);
- return max[0] as unknown as FontSizeStyle;
-};
-
-export const FontSizeConfig = ({ app, shapes }: FontSizeConfigProps) => {
- const setFontSize = (size: FontSizeStyle) => {
- app.style({ fontSize: size }, getShapeIds(shapes));
- };
-
- const fontSize = _getFontSize(shapes);
- const selected =
- _fontSizes.find(f => f.value === fontSize) || _fontSizes[3];
-
- return (
-
- {_fontSizes.map(fontSize => {
- return (
- setFontSize(fontSize.value)}
- >
- {fontSize.icon}
- {fontSize.name}
-
- );
- })}
-
- }
- >
-
- {selected.icon}
-
-
- );
-};
-
-export const ListItemContainer = styled('div')(({ theme }) => ({
- display: 'flex',
- alignItems: 'center',
- cursor: 'pointer',
- height: '32px',
- padding: '4px 12px',
- color: theme.affine.palette.icons,
-
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '&:hover': {
- backgroundColor: theme.affine.palette.hover,
- },
-}));
-
-export const ListItemTitle = styled('span')(({ theme }) => ({
- marginLeft: '12px',
- color: theme.affine.palette.primaryText,
-}));
diff --git a/libs/components/board-draw/src/components/command-panel/FrameFillColorConfig.tsx b/libs/components/board-draw/src/components/command-panel/FrameFillColorConfig.tsx
deleted file mode 100644
index 58a13927e..000000000
--- a/libs/components/board-draw/src/components/command-panel/FrameFillColorConfig.tsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import {
- ShapeColorDuotoneIcon,
- ShapeColorNoneIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- Popover,
- Tooltip,
- useTheme,
-} from '@toeverything/components/ui';
-import { countBy, maxBy } from '@toeverything/utils';
-import { Palette } from '../palette';
-import { getShapeIds } from './utils';
-
-interface BorderColorConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-type ColorType = 'none' | string;
-
-const _colors: ColorType[] = [
- 'rgba(255, 133, 137, 0.5)',
- 'rgba(255, 159, 101, 0.5)',
- 'rgba(255, 251, 69, 0.5)',
- 'rgba(64, 255, 138, 0.5)',
- 'rgba(26, 252, 255, 0.5)',
- 'rgba(198, 156, 255, 0.5)',
- 'rgba(255, 143, 224, 0.5)',
- 'rgba(152, 172, 189, 0.5)',
- 'rgba(216, 226, 248, 0.5)',
-];
-
-const _getIconRenderColor = (shapes: TDShape[]) => {
- const counted = countBy(shapes, shape => shape.style.fill);
- const max = maxBy(Object.entries(counted), ([c, n]) => n);
- return max[0];
-};
-
-export const FrameFillColorConfig = ({
- app,
- shapes,
-}: BorderColorConfigProps) => {
- const theme = useTheme();
- const setFillColor = (color: ColorType) => {
- app.style(
- { fill: color, isFilled: color !== 'none' },
- getShapeIds(shapes)
- );
- };
-
- const iconColor = _getIconRenderColor(shapes);
-
- return (
-
- }
- >
-
-
- {iconColor === 'none' ? (
-
- ) : (
-
- )}
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/GroupOperation.tsx b/libs/components/board-draw/src/components/command-panel/GroupOperation.tsx
deleted file mode 100644
index 47aaaefa9..000000000
--- a/libs/components/board-draw/src/components/command-panel/GroupOperation.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { TLDR, TldrawApp } from '@toeverything/components/board-state';
-import { TDShape, TDShapeType } from '@toeverything/components/board-types';
-import { GroupIcon, UngroupIcon } from '@toeverything/components/icons';
-import { IconButton, Tooltip } from '@toeverything/components/ui';
-import { services } from '@toeverything/datasource/db-service';
-import { getShapeIds } from './utils';
-
-interface GroupAndUnGroupProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-export const Group = ({ app, shapes }: GroupAndUnGroupProps) => {
- const group = async () => {
- let groupShape = await services.api.editorBlock.create({
- workspace: app.document.id,
- parentId: app.appState.currentPageId,
- type: 'shape',
- });
- await services.api.editorBlock.update({
- workspace: groupShape.workspace,
- id: groupShape.id,
- properties: {
- shapeProps: {
- value: JSON.stringify(
- TLDR.get_shape_util(TDShapeType.Group).create({
- id: groupShape.id,
- affineId: groupShape.id,
- childIndex: 1,
- parentId: app.appState.currentPageId,
- point: [0, 0],
- size: [0, 0],
- children: getShapeIds(shapes),
- workspace: app.document.id,
- })
- ),
- },
- },
- });
-
- app.group(
- getShapeIds(shapes),
- groupShape.id,
- app.appState.currentPageId
- );
- };
- return (
-
-
-
-
-
- );
-};
-
-export const UnGroup = ({ app, shapes }: GroupAndUnGroupProps) => {
- const ungroup = () => {
- app.ungroup(getShapeIds(shapes));
- };
-
- return (
-
-
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/LockOperation.tsx b/libs/components/board-draw/src/components/command-panel/LockOperation.tsx
deleted file mode 100644
index b7de04969..000000000
--- a/libs/components/board-draw/src/components/command-panel/LockOperation.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import { LockIcon, UnlockIcon } from '@toeverything/components/icons';
-import { IconButton, Tooltip } from '@toeverything/components/ui';
-import { getShapeIds } from './utils';
-
-interface GroupAndUnGroupProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-export const Lock = ({ app, shapes }: GroupAndUnGroupProps) => {
- const lock = () => {
- app.lock(getShapeIds(shapes));
- };
- return (
-
-
-
-
-
- );
-};
-
-export const Unlock = ({ app, shapes }: GroupAndUnGroupProps) => {
- const unlock = () => {
- app.unlock(getShapeIds(shapes));
- };
-
- return (
-
-
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/MoveCoverage.tsx b/libs/components/board-draw/src/components/command-panel/MoveCoverage.tsx
deleted file mode 100644
index cbca4224f..000000000
--- a/libs/components/board-draw/src/components/command-panel/MoveCoverage.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import {
- BringForwardIcon,
- BringToFrontIcon,
- LayersIcon,
- SendBackwardIcon,
- SendToBackIcon,
-} from '@toeverything/components/icons';
-import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
-import { AlignPanel } from '../align-panel';
-
-interface FontSizeConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-const AlignPanelArr = [
- {
- title: 'To Front',
- name: 'tofront',
- icon:
,
- },
- {
- title: 'Forward',
- name: 'forward',
- icon:
,
- },
- {
- title: 'Backward',
- name: 'backward',
- icon:
,
- },
- {
- title: 'To Back',
- name: 'toback',
- icon:
,
- },
-];
-
-export const MoveCoverageConfig = ({ app, shapes }: FontSizeConfigProps) => {
- const moveCoverage = (type: string) => {
- switch (type) {
- case 'toback':
- app.moveToBack();
- break;
- case 'backward':
- app.moveBackward();
- break;
- case 'forward':
- app.moveForward();
- break;
- case 'tofront':
- app.moveToFront();
- break;
- }
- };
-
- return (
-
- }
- >
-
-
-
-
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/index.ts b/libs/components/board-draw/src/components/command-panel/index.ts
deleted file mode 100644
index 5f46a20f6..000000000
--- a/libs/components/board-draw/src/components/command-panel/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { CommandPanel } from './CommandPanel';
diff --git a/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/LineStyle.tsx b/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/LineStyle.tsx
deleted file mode 100644
index b7d2a74b1..000000000
--- a/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/LineStyle.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import { DashStyle, StrokeWidth } from '@toeverything/components/board-types';
-import {
- BrushIcon,
- DashLineIcon,
- LineNoneIcon,
- SolidLineIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- Slider,
- styled,
- Tooltip,
-} from '@toeverything/components/ui';
-
-export const lineStyles = [
- {
- name: 'None',
- value: DashStyle.None,
- icon:
,
- },
- {
- name: 'Draw',
- value: DashStyle.Draw,
- icon:
,
- },
- {
- name: 'Solid',
- value: DashStyle.Solid,
- icon:
,
- },
- {
- name: 'Dash',
- value: DashStyle.Dashed,
- icon:
,
- },
-];
-
-interface LineStyleProps {
- strokeStyle: DashStyle;
- onStrokeStyleChange: (style: DashStyle) => void;
-
- strokeWidth: StrokeWidth;
- onStrokeWidthChange: (width: StrokeWidth) => void;
-}
-
-export const LineStyle = ({
- strokeStyle,
- onStrokeStyleChange,
- strokeWidth,
- onStrokeWidthChange,
-}: LineStyleProps) => {
- return (
-
- Stroke Style
-
- {lineStyles.map(lineStyle => {
- const active = lineStyle.value === strokeStyle;
- return (
-
- {
- onStrokeStyleChange(lineStyle.value);
- }}
- >
- {lineStyle.icon}
-
-
- );
- })}
-
- Thickness
-
- {
- onStrokeWidthChange(value as StrokeWidth);
- }}
- />
-
-
- );
-};
-
-const Container = styled('div')({
- width: '132px',
- margin: '0 10px',
-});
-
-const Title = styled('div')(({ theme }) => ({
- fontSize: '12px',
- lineHeight: '14px',
- color: theme.affine.palette.menu,
- margin: '6px 0',
-}));
-
-const StrokeStyleContainer = styled('div')({
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
-});
-
-const SliderContainer = styled('div')({
- padding: '0 6px',
-});
diff --git a/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/StrokeLineStyleConfig.tsx b/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/StrokeLineStyleConfig.tsx
deleted file mode 100644
index 3d90ac397..000000000
--- a/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/StrokeLineStyleConfig.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import { DashStyle, StrokeWidth } from '@toeverything/components/board-types';
-import { BrushIcon } from '@toeverything/components/icons';
-import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
-import { countBy, maxBy } from '@toeverything/utils';
-import { getShapeIds } from '../utils';
-import { LineStyle, lineStyles } from './LineStyle';
-
-const _getStrokeStyle = (shapes: TDShape[]): DashStyle => {
- const counted = countBy(shapes, shape => shape.style.dash);
- const max = maxBy(Object.entries(counted), ([c, n]) => n);
- return max[0] as DashStyle;
-};
-
-const _getStrokeWidth = (shapes: TDShape[]): StrokeWidth => {
- const counted = countBy(shapes, shape => shape.style.strokeWidth);
- const max = maxBy(Object.entries(counted), ([c, n]) => n);
- return Number(max[0]) as StrokeWidth;
-};
-interface BorderColorConfigProps {
- app: TldrawApp;
- shapes: TDShape[];
-}
-
-export const StrokeLineStyleConfig = ({
- app,
- shapes,
-}: BorderColorConfigProps) => {
- const strokeStyle = _getStrokeStyle(shapes);
- const strokeWidth = _getStrokeWidth(shapes);
- const setStrokeLineStyle = (style: DashStyle) => {
- app.style(
- {
- dash: style,
- },
- getShapeIds(shapes)
- );
- };
- const setStrokeLineWidth = (width: StrokeWidth) => {
- app.style(
- {
- strokeWidth: width,
- },
- getShapeIds(shapes)
- );
- };
-
- const icon = lineStyles.find(style => style.value === strokeStyle)
- ?.icon ||
;
-
- return (
-
- }
- >
-
- {icon}
-
-
- );
-};
diff --git a/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/index.ts b/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/index.ts
deleted file mode 100644
index d80d4df23..000000000
--- a/libs/components/board-draw/src/components/command-panel/stroke-line-style-config/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { StrokeLineStyleConfig } from './StrokeLineStyleConfig';
diff --git a/libs/components/board-draw/src/components/command-panel/utils/get-anchor.ts b/libs/components/board-draw/src/components/command-panel/utils/get-anchor.ts
deleted file mode 100644
index 52f10b9b7..000000000
--- a/libs/components/board-draw/src/components/command-panel/utils/get-anchor.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-interface ShapeRect {
- x: number;
- y: number;
- width: number;
- height: number;
-}
-
-export const getAnchor = (rect: ShapeRect) => {
- return {
- getBoundingClientRect(): DOMRect {
- const x = rect.x;
- const y = rect.y;
- const width = rect.width;
- const height = rect.height;
-
- const calcRect = {
- x,
- y,
- top: y,
- left: x,
- width,
- height,
- bottom: y + height,
- right: x + width,
- };
- const jsonStr = JSON.stringify(rect);
-
- return {
- ...calcRect,
- toJSON() {
- return jsonStr;
- },
- };
- },
- };
-};
diff --git a/libs/components/board-draw/src/components/command-panel/utils/index.ts b/libs/components/board-draw/src/components/command-panel/utils/index.ts
deleted file mode 100644
index 7b0c0f934..000000000
--- a/libs/components/board-draw/src/components/command-panel/utils/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { getAnchor } from './get-anchor';
-export { getShapeIds, useConfig } from './use-config';
diff --git a/libs/components/board-draw/src/components/command-panel/utils/use-config.ts b/libs/components/board-draw/src/components/command-panel/utils/use-config.ts
deleted file mode 100644
index 8236cbe36..000000000
--- a/libs/components/board-draw/src/components/command-panel/utils/use-config.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import type { TDShape } from '@toeverything/components/board-types';
-import { TDShapeType } from '@toeverything/components/board-types';
-
-interface Config {
- type:
- | 'stroke'
- | 'fill'
- | 'frameFill'
- | 'font'
- | 'group'
- | 'ungroup'
- | 'deleteShapes'
- | 'lock'
- | 'unlock';
- selectedShapes: TDShape[];
-}
-
-const _createInitConfig = (): Record
=> {
- return {
- fill: {
- type: 'fill',
- selectedShapes: [],
- },
- frameFill: {
- type: 'frameFill',
- selectedShapes: [],
- },
- stroke: {
- type: 'stroke',
- selectedShapes: [],
- },
- font: {
- type: 'font',
- selectedShapes: [],
- },
- group: {
- type: 'group',
- selectedShapes: [],
- },
- ungroup: {
- type: 'ungroup',
- selectedShapes: [],
- },
- deleteShapes: {
- type: 'deleteShapes',
- selectedShapes: [],
- },
- lock: {
- type: 'lock',
- selectedShapes: [],
- },
- unlock: {
- type: 'unlock',
- selectedShapes: [],
- },
- };
-};
-
-const _isSupportStroke = (shape: TDShape): boolean => {
- return [
- TDShapeType.Rectangle,
- TDShapeType.Ellipse,
- TDShapeType.Hexagon,
- TDShapeType.Triangle,
- TDShapeType.WhiteArrow,
- TDShapeType.Pentagram,
- TDShapeType.Pencil,
- TDShapeType.Laser,
- TDShapeType.Highlight,
- TDShapeType.Draw,
- TDShapeType.Arrow,
- TDShapeType.Line,
- ].some(type => type === shape.type);
-};
-
-const _isSupportFill = (shape: TDShape): boolean => {
- return [
- TDShapeType.Rectangle,
- TDShapeType.Ellipse,
- TDShapeType.Hexagon,
- TDShapeType.Triangle,
- TDShapeType.WhiteArrow,
- TDShapeType.Pentagram,
- ].some(type => type === shape.type);
-};
-
-const _isSupportFont = (shape: TDShape): boolean => {
- return [
- TDShapeType.Rectangle,
- TDShapeType.Ellipse,
- TDShapeType.Hexagon,
- TDShapeType.Triangle,
- TDShapeType.WhiteArrow,
- TDShapeType.Pentagram,
- ].some(type => type === shape.type);
-};
-
-const _isSupportFrameFill = (shape: TDShape): boolean => {
- return shape.type === TDShapeType.Frame;
-};
-
-export const useConfig = (app: TldrawApp): Record => {
- const state = app.useStore();
- const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId);
- const config = selectedShapes.reduce>(
- (acc, cur) => {
- if (_isSupportStroke(cur)) {
- acc.stroke.selectedShapes.push(cur);
- }
- if (_isSupportFill(cur)) {
- acc.fill.selectedShapes.push(cur);
- }
- if (_isSupportFont(cur)) {
- acc.font.selectedShapes.push(cur);
- }
- if (_isSupportFrameFill(cur)) {
- acc.frameFill.selectedShapes.push(cur);
- }
- return acc;
- },
- _createInitConfig()
- );
-
- // group
- if (
- selectedShapes.length === 1 &&
- selectedShapes[0].type === TDShapeType.Group
- ) {
- config.ungroup.selectedShapes = selectedShapes;
- }
- if (selectedShapes.length > 1) {
- config.group.selectedShapes = selectedShapes;
- }
-
- // lock
- if (selectedShapes.length === 1 && selectedShapes[0].isLocked) {
- config.unlock.selectedShapes = selectedShapes;
- } else {
- config.lock.selectedShapes = selectedShapes;
- }
-
- config.deleteShapes.selectedShapes = selectedShapes;
-
- return config;
-};
-
-export const getShapeIds = (shapes?: TDShape[]): string[] => {
- return (shapes || []).map(shape => shape.id).filter(id => !!id);
-};
diff --git a/libs/components/board-draw/src/components/context-menu/context-menu.tsx b/libs/components/board-draw/src/components/context-menu/context-menu.tsx
deleted file mode 100644
index a303f45df..000000000
--- a/libs/components/board-draw/src/components/context-menu/context-menu.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import type { ReactNode } from 'react';
-
-export const ContextMenu = ({ children }: { children: ReactNode }) => {
- return {children}
;
-};
diff --git a/libs/components/board-draw/src/components/context-menu/index.ts b/libs/components/board-draw/src/components/context-menu/index.ts
deleted file mode 100644
index 48de5b78d..000000000
--- a/libs/components/board-draw/src/components/context-menu/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './context-menu';
diff --git a/libs/components/board-draw/src/components/error-fallback/error-fallback.tsx b/libs/components/board-draw/src/components/error-fallback/error-fallback.tsx
deleted file mode 100644
index f3b076bbf..000000000
--- a/libs/components/board-draw/src/components/error-fallback/error-fallback.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import { styled } from '@toeverything/components/ui';
-import { FallbackProps } from 'react-error-boundary';
-import { useTldrawApp } from '../../hooks';
-
-export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
- const app = useTldrawApp();
-
- const refreshPage = () => {
- window.location.reload();
- resetErrorBoundary();
- };
-
- const copyError = () => {
- const textarea = document.createElement('textarea');
- textarea.value = error.message;
- document.body.appendChild(textarea);
- textarea.select();
- document.execCommand('copy');
- textarea.remove();
- };
-
- const downloadBackup = () => {
- app.saveProjectAs();
- };
-
- const resetDocument = () => {
- app.resetDocument();
- resetErrorBoundary();
- };
-
- return (
-
-
- We've encountered an error!
-
- {error.message}
-
-
- Copy Error
- Refresh Page
-
-
-
- Keep getting this error?{' '}
-
- Download your project
- {' '}
- as a backup and then{' '}
-
- reset the document
-
- .
-
-
-
- );
-}
-
-const Container = styled('div')({
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: '$canvas',
-});
-
-const InnerContainer = styled('div')({
- backgroundColor: '$panel',
- border: '1px solid $panelContrast',
- padding: '$5',
- borderRadius: 8,
- boxShadow: '$panel',
- maxWidth: 320,
- color: '$text',
- fontSize: '$2',
- textAlign: 'center',
- display: 'flex',
- flexDirection: 'column',
- gap: '$3',
- '& > pre': {
- marginTop: '$3',
- marginBottom: '$3',
- textAlign: 'left',
- whiteSpace: 'pre-wrap',
- backgroundColor: '$hover',
- padding: '$4',
- borderRadius: '$2',
- fontWeight: 500,
- },
- '& p': {
- lineHeight: 1.7,
- padding: '$5',
- margin: 0,
- },
- '& a': {
- color: '$text',
- cursor: 'pointer',
- textDecoration: 'underline',
- },
- '& hr': {
- marginLeft: '-$5',
- marginRight: '-$5',
- },
-});
diff --git a/libs/components/board-draw/src/components/error-fallback/index.ts b/libs/components/board-draw/src/components/error-fallback/index.ts
deleted file mode 100644
index 053407e13..000000000
--- a/libs/components/board-draw/src/components/error-fallback/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './error-fallback';
diff --git a/libs/components/board-draw/src/components/loading/index.ts b/libs/components/board-draw/src/components/loading/index.ts
deleted file mode 100644
index 22652ccf3..000000000
--- a/libs/components/board-draw/src/components/loading/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './loading';
diff --git a/libs/components/board-draw/src/components/loading/loading.tsx b/libs/components/board-draw/src/components/loading/loading.tsx
deleted file mode 100644
index 1245cc0bb..000000000
--- a/libs/components/board-draw/src/components/loading/loading.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { TDSnapshot } from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import { useTldrawApp } from '../../hooks';
-
-const loadingSelector = (s: TDSnapshot) => s.appState.isLoading;
-
-export function Loading() {
- const app = useTldrawApp();
- const isLoading = app.useStore(loadingSelector);
-
- return (
-
- Loading...
-
- );
-}
-
-const StyledLoadingPanelContainer = styled('div')({
- position: 'absolute',
- top: 0,
- left: '50%',
- transform: `translate(-50%, 0)`,
- borderBottomLeftRadius: '12px',
- borderBottomRightRadius: '12px',
- padding: '8px 16px',
- fontSize: 'var(--fontSizes-1)',
- boxShadow: 'var(--shadows-panel)',
- backgroundColor: 'white',
- zIndex: 200,
- pointerEvents: 'none',
- '& > div > *': {
- pointerEvents: 'all',
- },
- variants: {
- transform: {
- hidden: {
- transform: `translate(-50%, 100%)`,
- },
- visible: {
- transform: `translate(-50%, 0%)`,
- },
- },
- },
-});
diff --git a/libs/components/board-draw/src/components/palette/Palette.tsx b/libs/components/board-draw/src/components/palette/Palette.tsx
deleted file mode 100644
index 5ff84e8ca..000000000
--- a/libs/components/board-draw/src/components/palette/Palette.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { ShapeColorNoneIcon } from '@toeverything/components/icons';
-import { styled, Tooltip } from '@toeverything/components/ui';
-import { useMemo } from 'react';
-
-interface ColorObject {
- name?: string;
- /**
- * color: none means no color
- */
- color: string;
-}
-/**
- * ColorValue : none means no color
- */
-type ColorValue = string | ColorObject;
-interface PaletteProps {
- colors: ColorValue[];
- selected?: string;
- onSelect?: (color: string) => void;
-}
-
-const formatColors = (colors: ColorValue[]): ColorObject[] => {
- return colors.map(color => {
- return typeof color === 'string' ? { color } : color;
- });
-};
-
-export const Palette = ({
- colors: propColors,
- selected,
- onSelect,
-}: PaletteProps) => {
- const colorObjects = useMemo(() => formatColors(propColors), [propColors]);
- return (
-
- {colorObjects.map(colorObject => {
- const color = colorObject.color;
- const selectedThisColor = selected === color;
- return (
-
- {
- onSelect?.(color);
- }}
- >
- {color === 'none' ? (
-
- ) : (
-
- )}
-
-
- );
- })}
-
- );
-};
-
-const Container = styled('div')({
- width: '120px',
- display: 'flex',
- flexWrap: 'wrap',
-});
-
-const SelectableContainer = styled('div')<{ selected?: boolean }>(
- ({ selected, theme }) => ({
- width: '20px',
- height: '20px',
- border: `1px solid ${
- selected ? theme.affine.palette.primary : 'rgba(0,0,0,0)'
- }`,
- borderRadius: '5px',
- overflow: 'hidden',
- margin: '10px',
- padding: '1px',
- cursor: 'pointer',
- boxSizing: 'border-box',
- })
-);
-
-const Color = styled('div')({
- width: '16px',
- height: '16px',
- borderRadius: '5px',
- boxShadow: '0px 0px 2px rgba(0, 0, 0, 0.25)',
-});
-
-const StyledShapeColorNoneIcon = styled(ShapeColorNoneIcon)(({ theme }) => ({
- position: 'relative',
- left: '-4px',
- top: '-4px',
- color: theme.affine.palette.icons,
-}));
diff --git a/libs/components/board-draw/src/components/palette/index.ts b/libs/components/board-draw/src/components/palette/index.ts
deleted file mode 100644
index 54d45c974..000000000
--- a/libs/components/board-draw/src/components/palette/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { Palette } from './Palette';
diff --git a/libs/components/board-draw/src/components/tools-panel/LineTools.tsx b/libs/components/board-draw/src/components/tools-panel/LineTools.tsx
deleted file mode 100644
index ce6cb9757..000000000
--- a/libs/components/board-draw/src/components/tools-panel/LineTools.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import {
- ConectorArrowIcon,
- ConectorLineIcon,
- ConnectorIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- Popover,
- styled,
- Tooltip,
-} from '@toeverything/components/ui';
-import { useEffect, useState } from 'react';
-
-import { TldrawApp } from '@toeverything/components/board-state';
-import { TDShapeType, TDSnapshot } from '@toeverything/components/board-types';
-
-export type ShapeTypes = TDShapeType.Line | TDShapeType.Arrow;
-
-const shapes = [
- {
- type: TDShapeType.Line,
- label: 'Line',
- tooltip: 'Line',
- icon: ConectorLineIcon,
- },
- {
- type: TDShapeType.Arrow,
- label: 'Arrow',
- tooltip: 'Arrow',
- icon: ConectorArrowIcon,
- },
-] as const;
-
-const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
-
-export const LineTools = ({ app }: { app: TldrawApp }) => {
- const activeTool = app.useStore(activeToolSelector);
- const [visible, setVisible] = useState(false);
-
- const [lastActiveTool, setLastActiveTool] = useState(
- TDShapeType.Line
- );
-
- useEffect(() => {
- if (
- shapes.find(s => s.type === activeTool) &&
- lastActiveTool !== activeTool
- ) {
- setLastActiveTool(activeTool as ShapeTypes);
- }
- }, [activeTool, lastActiveTool]);
-
- return (
- setVisible(prev => !prev)}
- onClickAway={() => setVisible(false)}
- content={
-
- {shapes.map(({ type, label, tooltip, icon: Icon }) => (
-
- {
- app.selectTool(type);
- setVisible(false);
- setLastActiveTool(type);
- }}
- >
-
-
-
- ))}
-
- }
- >
-
-
-
-
-
-
- );
-};
-
-const ShapesContainer = styled('div')({
- width: '64px',
- display: 'flex',
- flexWrap: 'wrap',
-});
diff --git a/libs/components/board-draw/src/components/tools-panel/ShapeTools.tsx b/libs/components/board-draw/src/components/tools-panel/ShapeTools.tsx
deleted file mode 100644
index 0c04126c9..000000000
--- a/libs/components/board-draw/src/components/tools-panel/ShapeTools.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import {
- ArrowIcon,
- EllipseIcon,
- PolygonIcon,
- RectangleIcon,
- ShapeIcon,
- StarIcon,
- TriangleIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- Popover,
- styled,
- Tooltip,
-} from '@toeverything/components/ui';
-import { useEffect, useState } from 'react';
-
-import { TldrawApp } from '@toeverything/components/board-state';
-import { TDShapeType, TDSnapshot } from '@toeverything/components/board-types';
-
-export type ShapeTypes =
- | TDShapeType.Rectangle
- | TDShapeType.Ellipse
- | TDShapeType.Triangle
- | TDShapeType.Line
- | TDShapeType.Hexagon
- | TDShapeType.Pentagram
- | TDShapeType.WhiteArrow
- | TDShapeType.Arrow;
-
-const shapes = [
- {
- type: TDShapeType.Rectangle,
- label: 'Rectangle',
- tooltip: 'Rectangle',
- icon: RectangleIcon,
- },
- {
- type: TDShapeType.WhiteArrow,
- label: 'WhiteArrow',
- tooltip: 'WhiteArrow',
- icon: ArrowIcon,
- },
- {
- type: TDShapeType.Triangle,
- label: 'Triangle',
- tooltip: 'Triangle',
- icon: TriangleIcon,
- },
- {
- type: TDShapeType.Hexagon,
- label: 'InvertedTranslate',
- tooltip: 'InvertedTranslate',
- icon: PolygonIcon,
- },
- {
- type: TDShapeType.Pentagram,
- label: 'Pentagram',
- tooltip: 'Pentagram',
- icon: StarIcon,
- },
- {
- type: TDShapeType.Ellipse,
- label: 'Ellipse',
- tooltip: 'Ellipse',
- icon: EllipseIcon,
- },
-] as const;
-
-const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
-
-export const ShapeTools = ({ app }: { app: TldrawApp }) => {
- const activeTool = app.useStore(activeToolSelector);
- const [visible, setVisible] = useState(false);
-
- const [lastActiveTool, setLastActiveTool] = useState(
- TDShapeType.Rectangle
- );
-
- useEffect(() => {
- if (
- shapes.find(s => s.type === activeTool) &&
- lastActiveTool !== activeTool
- ) {
- setLastActiveTool(activeTool as ShapeTypes);
- }
- }, [activeTool, lastActiveTool]);
-
- return (
- setVisible(prev => !prev)}
- onClickAway={() => setVisible(false)}
- content={
-
- {shapes.map(({ type, label, tooltip, icon: Icon }) => (
-
- {
- app.selectTool(type);
- setVisible(false);
- setLastActiveTool(type);
- }}
- >
-
-
-
- ))}
-
- }
- >
-
-
-
-
-
-
- );
-};
-
-const ShapesContainer = styled('div')({
- width: '64px',
- display: 'flex',
- flexWrap: 'wrap',
-});
diff --git a/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx b/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx
deleted file mode 100644
index 82a531581..000000000
--- a/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import {
- EraserIcon,
- EraserIconProps,
- FrameIcon,
- HandToolIcon,
- HandToolIconProps,
- SelectIcon,
- SelectIconProps,
- TextIcon,
- TextIconProps,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- PopoverContainer,
- styled,
- // MuiIconButton as IconButton,
- // MuiTooltip as Tooltip,
- Tooltip,
- useTheme,
-} from '@toeverything/components/ui';
-
-import { TldrawApp } from '@toeverything/components/board-state';
-import {
- TDShapeType,
- TDSnapshot,
- TDToolType,
-} from '@toeverything/components/board-types';
-
-import { ComponentType } from 'react';
-import { LineTools } from './LineTools';
-import { PenTools } from './pen-tools';
-import { ShapeTools } from './ShapeTools';
-
-const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
-const toolLockedSelector = (s: TDSnapshot) => s.appState.isToolLocked;
-
-const tools: Array<{
- type: string;
- label?: string;
- tooltip?: string;
- icon?: ComponentType<
- SelectIconProps | EraserIconProps | HandToolIconProps | TextIconProps
- >;
- component?: ComponentType<{ app: TldrawApp }>;
-}> = [
- {
- type: 'select',
- label: 'Select',
- tooltip: 'Select',
- icon: SelectIcon,
- },
- { type: 'frame', label: 'Frame', tooltip: 'Frame', icon: FrameIcon },
- {
- type: TDShapeType.Editor,
- label: 'Text Block',
- tooltip: 'Text Block',
- icon: TextIcon,
- },
- { type: 'shapes', component: ShapeTools },
- { type: 'draw', component: PenTools },
- { type: 'Connector', component: LineTools },
- // { type: 'erase', label: 'Erase', tooltip: 'Erase', icon: EraseIcon },
- {
- type: TDShapeType.HandDrag,
- label: 'Hand Drag',
- tooltip: 'Hand Drag',
- icon: HandToolIcon,
- },
- {
- type: 'erase',
- label: 'Eraser',
- tooltip: 'Eraser',
- icon: EraserIcon,
- },
-];
-
-export const ToolsPanel = ({ app }: { app: TldrawApp }) => {
- const activeTool = app.useStore(activeToolSelector);
-
- const isToolLocked = app.useStore(toolLockedSelector);
- const theme = useTheme();
-
- return (
-
-
-
- {tools.map(
- ({
- type,
- label,
- tooltip,
- icon: Icon,
- component: Component,
- }) =>
- Component ? (
-
- ) : (
-
- {
- app.selectTool(type as TDToolType);
- }}
- disabled={isToolLocked}
- >
-
-
-
- )
- )}
-
-
-
- );
-};
-
-const Container = styled('div')({
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- height: '100%',
-});
-const ToolBar = styled('div')({
- display: 'flex',
- flexDirection: 'column',
- backgroundColor: '#ffffff',
- borderRadius: '10px',
- padding: '4px 4px',
-});
diff --git a/libs/components/board-draw/src/components/tools-panel/index.ts b/libs/components/board-draw/src/components/tools-panel/index.ts
deleted file mode 100644
index daf89a7e0..000000000
--- a/libs/components/board-draw/src/components/tools-panel/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { ToolsPanel } from './ToolsPanel';
diff --git a/libs/components/board-draw/src/components/tools-panel/pen-tools/Pen.tsx b/libs/components/board-draw/src/components/tools-panel/pen-tools/Pen.tsx
deleted file mode 100644
index bdddb055a..000000000
--- a/libs/components/board-draw/src/components/tools-panel/pen-tools/Pen.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { IconButton, styled, Tooltip } from '@toeverything/components/ui';
-import type { ReactNode } from 'react';
-
-interface PenProps {
- name: string;
- icon: ReactNode;
- primaryColor: string;
- secondaryColor: string;
- onClick: () => void;
-}
-
-export const Pen = ({
- name,
- icon,
- primaryColor,
- secondaryColor,
- onClick,
-}: PenProps) => {
- return (
-
-
- {icon}
-
-
- );
-};
-
-const StyledIconButton = styled(IconButton, {
- shouldForwardProp: propName =>
- !['primaryColor', 'secondaryColor'].some(name => name === propName),
-})>(
- ({ primaryColor, secondaryColor }) => {
- return {
- '--color-0': primaryColor,
- '--color-1': secondaryColor,
- width: '40px',
- height: '40px',
- };
- }
-);
diff --git a/libs/components/board-draw/src/components/tools-panel/pen-tools/PenTools.tsx b/libs/components/board-draw/src/components/tools-panel/pen-tools/PenTools.tsx
deleted file mode 100644
index c955afba0..000000000
--- a/libs/components/board-draw/src/components/tools-panel/pen-tools/PenTools.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import { TldrawApp } from '@toeverything/components/board-state';
-import { TDShapeType, TDToolType } from '@toeverything/components/board-types';
-import {
- HighlighterDuotoneIcon,
- LaserPenDuotoneIcon,
- PencilDuotoneIcon,
-} from '@toeverything/components/icons';
-import {
- IconButton,
- MuiDivider as Divider,
- Popover,
- styled,
- Tooltip,
-} from '@toeverything/components/ui';
-import { useState, type CSSProperties, type ReactElement } from 'react';
-import { Palette } from '../../palette';
-import { Pen } from './Pen';
-
-type PenType = TDShapeType.Pencil | TDShapeType.Highlight | TDShapeType.Laser;
-
-interface PencilConfig {
- key: PenType;
- title: string;
- icon: ReactElement;
- colors: string[];
- getColorVars: (
- primaryColor: string,
- secondaryColor: string
- ) => { '--color-0': string; '--color-1': string };
-}
-
-const PENCIL_CONFIGS: PencilConfig[] = [
- {
- key: TDShapeType.Pencil,
- title: 'Pencil',
- icon: ,
- colors: [
- '#F1675E',
- '#FF7F22',
- '#FFCB45',
- '#40DF9B',
- '#13D9E3',
- '#3E6FDB',
- '#7352F1',
- '#3A4C5C',
- '#FFFFFF',
- ],
- getColorVars: (primaryColor: string, secondaryColor: string) => {
- return {
- '--color-0': secondaryColor,
- '--color-1': primaryColor,
- };
- },
- },
- {
- key: TDShapeType.Highlight,
- title: 'Highlighter',
- icon: ,
- colors: [
- 'rgba(255, 133, 137, 0.5)',
- 'rgba(255, 159, 101, 0.5)',
- 'rgba(255, 251, 69, 0.5)',
- 'rgba(64, 255, 138, 0.5)',
- 'rgba(26, 252, 255, 0.5)',
- 'rgba(198, 156, 255, 0.5)',
- 'rgba(255, 143, 224, 0.5)',
- 'rgba(152, 172, 189, 0.5)',
- 'rgba(216, 226, 248, 0.5)',
- ],
- getColorVars: (primaryColor: string, secondaryColor: string) => {
- return {
- '--color-0': secondaryColor,
- '--color-1': primaryColor,
- };
- },
- },
- {
- key: TDShapeType.Laser,
- title: 'Laser',
- icon: ,
- colors: [
- '#DB3E3E',
- '#FF5F1A',
- '#FFA800',
- '#13D9E3',
- '#00ADCE',
- '#3E6FDB',
- '#2F5DC2',
- '#153C7A',
- ],
- getColorVars: (primaryColor: string, secondaryColor: string) => {
- return {
- '--color-0': primaryColor,
- '--color-1': secondaryColor,
- };
- },
- },
-];
-
-const PENCIL_CONFIGS_MAP = PENCIL_CONFIGS.reduce<
- Record
->((acc, cur) => {
- acc[cur.key] = cur;
- return acc;
-}, {} as Record);
-
-export const PenTools = ({ app }: { app: TldrawApp }) => {
- const appCurrentTool = app.useStore(state => state.appState.activeTool);
- const [visible, setVisible] = useState(false);
- const chosenPen =
- PENCIL_CONFIGS.find(config => config.key === appCurrentTool) ||
- PENCIL_CONFIGS[0];
- const chosenPenKey = chosenPen.key;
- const currentColor = app.useStore(
- state => state.appState.currentStyle.stroke
- );
- const chosenColor = chosenPen.colors.includes(currentColor)
- ? currentColor
- : chosenPen.colors[0];
- const isActiveTool = appCurrentTool === chosenPenKey;
-
- const setPen = (pen: PenType) => {
- app.selectTool(pen);
- const penConfig = PENCIL_CONFIGS_MAP[pen];
- if (!penConfig.colors.includes(currentColor)) {
- setPenColor(penConfig.colors[0]);
- }
- };
-
- const setPenColor = (color: string) => {
- app.style({
- stroke: color,
- });
- };
-
- return (
- setVisible(prev => !prev)}
- onClickAway={() => setVisible(false)}
- content={
-
-
- {PENCIL_CONFIGS.map(
- ({ title, icon, key, getColorVars }) => {
- const active = chosenPenKey === key;
- const color_vars = getColorVars(
- active ? '#3A4C5C' : '#98ACBD',
- active ? chosenColor : '#D8E2F8'
- );
- return (
- {
- setVisible(false);
- setPen(key);
- }}
- />
- );
- }
- )}
-
-
- {
- setVisible(false);
- setPenColor(color);
- }}
- />
-
-
- }
- >
-
- {
- setPen(chosenPen.key);
- }}
- style={{
- ...(chosenPen.getColorVars(
- isActiveTool ? '#3A4C5C' : '#98ACBD',
- isActiveTool ? chosenColor : '#D8E2F8'
- ) as CSSProperties),
- }}
- >
- {chosenPen.icon}
-
-
-
- );
-};
-
-const Container = styled('div')({
- padding: 0,
-});
-
-const PensContainer = styled('div')({
- display: 'flex',
- justifyContent: 'space-between',
-});
diff --git a/libs/components/board-draw/src/components/tools-panel/pen-tools/index.ts b/libs/components/board-draw/src/components/tools-panel/pen-tools/index.ts
deleted file mode 100644
index 1bf4bd192..000000000
--- a/libs/components/board-draw/src/components/tools-panel/pen-tools/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { PenTools } from './PenTools';
diff --git a/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx b/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx
deleted file mode 100644
index c3d1856d9..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/ZoomBar.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import AddIcon from '@mui/icons-material/Add';
-import RemoveIcon from '@mui/icons-material/Remove';
-import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
-import {
- MuiButton as Button,
- MuiIconButton as IconButton,
- styled,
-} from '@toeverything/components/ui';
-
-import { TDSnapshot } from '@toeverything/components/board-types';
-import { useTldrawApp } from '../../hooks';
-
-import { MiniMap } from './mini-map';
-
-const zoomSelector = (s: TDSnapshot) =>
- s.document.pageStates[s.appState.currentPageId].camera.zoom;
-
-export const ZoomBar = () => {
- const app = useTldrawApp();
- const zoom = app.useStore(zoomSelector);
-
- return (
-
-
-
-
-
-
-
- {Math.round(zoom * 100)}%
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const ZoomBarContainer = styled('div')({
- position: 'absolute',
- right: 10,
- bottom: 10,
- zIndex: 200,
- userSelect: 'none',
-});
-
-const MiniMapContainer = styled('div')({
- display: 'flex',
- justifyContent: 'flex-end',
-});
diff --git a/libs/components/board-draw/src/components/zoom-bar/index.ts b/libs/components/board-draw/src/components/zoom-bar/index.ts
deleted file mode 100644
index 1c3eb6b43..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { ZoomBar } from './ZoomBar';
diff --git a/libs/components/board-draw/src/components/zoom-bar/mini-map/MiniMap.tsx b/libs/components/board-draw/src/components/zoom-bar/mini-map/MiniMap.tsx
deleted file mode 100644
index dbe107660..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/mini-map/MiniMap.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import { TLDR } from '@toeverything/components/board-state';
-import { styled } from '@toeverything/components/ui';
-import { useTldrawApp } from '../../../hooks';
-
-import { getViewportBound, processBound } from './bounds';
-import { SimplifiedShape } from './SimplifiedShape';
-import { Viewport } from './Viewport';
-
-const MINI_MAP_WIDTH = 150;
-const MINI_MAP_HEIGHT = 100;
-
-const getScaleToMap = (width: number, height: number) => {
- const scaleWidth = width / MINI_MAP_WIDTH;
- const scaleHeight = height / MINI_MAP_HEIGHT;
- return scaleWidth > scaleHeight ? scaleWidth : scaleHeight;
-};
-
-export const MiniMap = () => {
- const app = useTldrawApp();
- const page = app.useStore(s => s.document.pages[s.appState.currentPageId]);
- const pageState = app.useStore(
- s => s.document.pageStates[s.appState.currentPageId]
- );
- const viewportBound = getViewportBound(
- app.rendererBounds,
- pageState.camera
- );
-
- const shapes = Object.values(page.shapes);
- const bounds = shapes.map(shape => TLDR.get_bounds(shape));
- const commonBound = Utils.getCommonBounds(bounds.concat(viewportBound));
- const scaleToMap = commonBound
- ? getScaleToMap(commonBound.width, commonBound.height)
- : 1;
- const processedViewportBound = processBound({
- bound: viewportBound,
- scale: scaleToMap,
- commonBound,
- });
-
- return (
-
-
- {shapes.map((shape, index) => {
- const bound = processBound({
- bound: bounds[index],
- scale: scaleToMap,
- commonBound,
- });
- return (
- {
- app.zoomToShapes([shape]);
- }}
- />
- );
- })}
- {
- app.pan(Vec.mul(delta, scaleToMap));
- }}
- />
-
-
- );
-};
-
-const Container = styled('div')(({ theme }) => ({
- display: 'inline-block',
- padding: '10px',
- borderColor: theme.affine.palette.borderColor,
- borderWidth: '1px',
- borderStyle: 'solid',
- borderRadius: '4px',
- backgroundColor: theme.affine.palette.white,
-}));
-
-const ShapesContainer = styled('div')({
- position: 'relative',
- width: `${MINI_MAP_WIDTH}px`,
- height: `${MINI_MAP_HEIGHT}px`,
-});
diff --git a/libs/components/board-draw/src/components/zoom-bar/mini-map/SimplifiedShape.tsx b/libs/components/board-draw/src/components/zoom-bar/mini-map/SimplifiedShape.tsx
deleted file mode 100644
index c35b148c2..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/mini-map/SimplifiedShape.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import type { TLBounds } from '@tldraw/core';
-import { styled } from '@toeverything/components/ui';
-import type { CSSProperties } from 'react';
-
-interface SimplifiedShapeProps extends TLBounds {
- onClick?: () => void;
-}
-
-export const SimplifiedShape = ({
- onClick,
- width,
- height,
- minX,
- minY,
-}: SimplifiedShapeProps) => {
- const style: CSSProperties = {
- width: `${width}px`,
- height: `${height}px`,
- left: `${minX}px`,
- top: `${minY}px`,
- };
- return ;
-};
-
-const Container = styled('div')(({ theme }) => ({
- position: 'absolute',
- backgroundColor: theme.affine.palette.icons,
- opacity: 0.2,
- cursor: 'pointer',
-
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '&:hover': {
- opacity: 0.8,
- },
-}));
diff --git a/libs/components/board-draw/src/components/zoom-bar/mini-map/Viewport.tsx b/libs/components/board-draw/src/components/zoom-bar/mini-map/Viewport.tsx
deleted file mode 100644
index 5b4283cb0..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/mini-map/Viewport.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import type { TLBounds } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import { alpha, styled } from '@toeverything/components/ui';
-import type { CSSProperties, PointerEventHandler } from 'react';
-import { useRef, useState } from 'react';
-
-interface ViewportProps extends TLBounds {
- onPan?: (delta: [number, number]) => void;
-}
-
-export const Viewport = ({
- onPan,
- width,
- height,
- minX,
- minY,
-}: ViewportProps) => {
- const style: CSSProperties = {
- width: `${width}px`,
- height: `${height}px`,
- left: `${minX}px`,
- top: `${minY}px`,
- };
- const [dragging, setDragging] = useState(false);
- const lastPosition = useRef<[number, number]>([0, 0]);
- const onPointerDown: PointerEventHandler = e => {
- setDragging(true);
- lastPosition.current = [e.clientX, e.clientY];
- const onPointerMove = (ev: PointerEvent) => {
- const newPosition = [ev.clientX, ev.clientY];
- const delta = Vec.sub(newPosition, lastPosition.current);
- lastPosition.current = newPosition as [number, number];
- onPan?.(delta as [number, number]);
- };
- const onPointerUp = () => {
- lastPosition.current = [0, 0];
- setDragging(false);
- document.removeEventListener('pointermove', onPointerMove);
- document.removeEventListener('pointerup', onPointerUp);
- };
- document.addEventListener('pointermove', onPointerMove);
- document.addEventListener('pointerup', onPointerUp);
- };
- return (
-
- );
-};
-
-const Container = styled('div')<{ dragging?: boolean }>(
- ({ theme, dragging }) => ({
- position: 'absolute',
- borderColor: theme.affine.palette.primary,
- borderWidth: '1px',
- borderStyle: 'solid',
- cursor: 'pointer',
- backgroundColor: dragging
- ? alpha(theme.affine.palette.icons, 0.2)
- : 'unset',
-
- // eslint-disable-next-line @typescript-eslint/naming-convention
- '&:hover': {
- backgroundColor: alpha(theme.affine.palette.icons, 0.2),
- },
- })
-);
diff --git a/libs/components/board-draw/src/components/zoom-bar/mini-map/bounds.ts b/libs/components/board-draw/src/components/zoom-bar/mini-map/bounds.ts
deleted file mode 100644
index 786b4dd09..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/mini-map/bounds.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import type { TLBounds, TLPageState } from '@tldraw/core';
-
-const getOffsetBound = (bound: TLBounds, commonBound: TLBounds): TLBounds => {
- return {
- width: bound.width,
- height: bound.height,
- minX: bound.minX - commonBound.minX,
- maxX: bound.maxX - commonBound.maxX,
- minY: bound.minY - commonBound.minY,
- maxY: bound.maxY - commonBound.maxY,
- };
-};
-
-const getScaledBound = (bound: TLBounds, scale: number): TLBounds => {
- return {
- width: bound.width / scale,
- height: bound.height / scale,
- minX: bound.minX / scale,
- maxX: bound.maxX / scale,
- minY: bound.minY / scale,
- maxY: bound.maxY / scale,
- };
-};
-
-interface ProcessBoundProps {
- bound: TLBounds;
- scale: number;
- commonBound: TLBounds;
-}
-
-export const processBound = ({
- bound,
- scale,
- commonBound,
-}: ProcessBoundProps) => {
- let boundResult = bound;
- boundResult = getOffsetBound(boundResult, commonBound);
- boundResult = getScaledBound(boundResult, scale);
- return boundResult;
-};
-
-export const getViewportBound = (
- rendererBounds: TLBounds,
- camera: TLPageState['camera']
-): TLBounds => {
- const [cameraX, cameraY] = camera.point;
- const zoom = camera.zoom;
- const minX = 0 - cameraX;
- const minY = 0 - cameraY;
- const width = rendererBounds.width / zoom;
- const height = rendererBounds.height / zoom;
- return {
- width,
- height,
- minX,
- maxX: minX + width,
- minY,
- maxY: minY + height,
- };
-};
diff --git a/libs/components/board-draw/src/components/zoom-bar/mini-map/index.ts b/libs/components/board-draw/src/components/zoom-bar/mini-map/index.ts
deleted file mode 100644
index b0392e643..000000000
--- a/libs/components/board-draw/src/components/zoom-bar/mini-map/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { MiniMap } from './MiniMap';
diff --git a/libs/components/board-draw/src/constants.ts b/libs/components/board-draw/src/constants.ts
deleted file mode 100644
index ffb5be480..000000000
--- a/libs/components/board-draw/src/constants.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import type { Easing } from '@toeverything/components/board-types';
-
-/* eslint-disable @typescript-eslint/no-explicit-any */
-export const LETTER_SPACING = '-0.03em';
-export const LINE_HEIGHT = 1;
-export const GRID_SIZE = 8;
-export const SVG_EXPORT_PADDING = 16;
-export const BINDING_DISTANCE = 16;
-export const CLONING_DISTANCE = 32;
-export const FIT_TO_SCREEN_PADDING = 128;
-export const SNAP_DISTANCE = 5;
-export const EMPTY_ARRAY = [] as any[];
-export const SLOW_SPEED = 10;
-export const VERY_SLOW_SPEED = 2.5;
-export const GHOSTED_OPACITY = 0.3;
-export const DEAD_ZONE = 3;
-export const LABEL_POINT = [0.5, 0.5];
-
-export const PI2 = Math.PI * 2;
-
-export const EASINGS: Record number> = {
- linear: t => t,
- easeInQuad: t => t * t,
- easeOutQuad: t => t * (2 - t),
- easeInOutQuad: t => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
- easeInCubic: t => t * t * t,
- easeOutCubic: t => --t * t * t + 1,
- easeInOutCubic: t =>
- t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
- easeInQuart: t => t * t * t * t,
- easeOutQuart: t => 1 - --t * t * t * t,
- easeInOutQuart: t =>
- t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t,
- easeInQuint: t => t * t * t * t * t,
- easeOutQuint: t => 1 + --t * t * t * t * t,
- easeInOutQuint: t =>
- t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t,
- easeInSine: t => 1 - Math.cos((t * Math.PI) / 2),
- easeOutSine: t => Math.sin((t * Math.PI) / 2),
- easeInOutSine: t => -(Math.cos(Math.PI * t) - 1) / 2,
- easeInExpo: t => (t <= 0 ? 0 : Math.pow(2, 10 * t - 10)),
- easeOutExpo: t => (t >= 1 ? 1 : 1 - Math.pow(2, -10 * t)),
- easeInOutExpo: t =>
- t <= 0
- ? 0
- : t >= 1
- ? 1
- : t < 0.5
- ? Math.pow(2, 20 * t - 10) / 2
- : (2 - Math.pow(2, -20 * t + 10)) / 2,
-};
-
-export const EASING_STRINGS: Record = {
- linear: `(t) => t`,
- easeInQuad: `(t) => t * t`,
- easeOutQuad: `(t) => t * (2 - t)`,
- easeInOutQuad: `(t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t)`,
- easeInCubic: `(t) => t * t * t`,
- easeOutCubic: `(t) => --t * t * t + 1`,
- easeInOutCubic: `(t) => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1`,
- easeInQuart: `(t) => t * t * t * t`,
- easeOutQuart: `(t) => 1 - --t * t * t * t`,
- easeInOutQuart: `(t) => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t`,
- easeInQuint: `(t) => t * t * t * t * t`,
- easeOutQuint: `(t) => 1 + --t * t * t * t * t`,
- easeInOutQuint: `(t) => t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t`,
- easeInSine: `(t) => 1 - Math.cos((t * Math.PI) / 2)`,
- easeOutSine: `(t) => Math.sin((t * Math.PI) / 2)`,
- easeInOutSine: `(t) => -(Math.cos(Math.PI * t) - 1) / 2`,
- easeInExpo: `(t) => (t <= 0 ? 0 : Math.pow(2, 10 * t - 10))`,
- easeOutExpo: `(t) => (t >= 1 ? 1 : 1 - Math.pow(2, -10 * t))`,
- easeInOutExpo: `(t) => t <= 0 ? 0 : t >= 1 ? 1 : t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2`,
-};
-
-export const USER_COLORS = [
- '#EC5E41',
- '#F2555A',
- '#F04F88',
- '#E34BA9',
- '#BD54C6',
- '#9D5BD2',
- '#7B66DC',
- '#02B1CC',
- '#11B3A3',
- '#39B178',
- '#55B467',
- '#FF802B',
-];
-
-export const isSafari =
- typeof Window === 'undefined'
- ? false
- : /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
-
-export const IMAGE_EXTENSIONS = ['.png', '.svg', '.jpg', '.jpeg', '.gif'];
-
-export const VIDEO_EXTENSIONS = isSafari ? [] : ['.mp4', '.webm'];
diff --git a/libs/components/board-draw/src/hooks/index.ts b/libs/components/board-draw/src/hooks/index.ts
deleted file mode 100644
index 9b6268253..000000000
--- a/libs/components/board-draw/src/hooks/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export * from './use-file-system-handlers';
-export * from './use-keyboard-shortcuts';
-// export * from './useTheme';
-export * from './use-stylesheet';
-export * from './use-tldraw-app';
-// export * from './useFileSystem';
diff --git a/libs/components/board-draw/src/hooks/use-file-system-handlers.ts b/libs/components/board-draw/src/hooks/use-file-system-handlers.ts
deleted file mode 100644
index ad4febda3..000000000
--- a/libs/components/board-draw/src/hooks/use-file-system-handlers.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as React from 'react';
-import { useTldrawApp } from './use-tldraw-app';
-
-export function useFileSystemHandlers() {
- const app = useTldrawApp();
-
- const onNewProject = React.useCallback(
- async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
- if (e && app.callbacks.onOpenProject) e.preventDefault();
- app.callbacks.onNewProject?.(app);
- },
- [app]
- );
-
- const onSaveProject = React.useCallback(
- (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
- if (e && app.callbacks.onOpenProject) e.preventDefault();
- app.callbacks.onSaveProject?.(app);
- },
- [app]
- );
-
- const onSaveProjectAs = React.useCallback(
- (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
- if (e && app.callbacks.onOpenProject) e.preventDefault();
- app.callbacks.onSaveProjectAs?.(app);
- },
- [app]
- );
-
- const onOpenProject = React.useCallback(
- async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
- if (e && app.callbacks.onOpenProject) e.preventDefault();
- app.callbacks.onOpenProject?.(app);
- },
- [app]
- );
-
- const onOpenMedia = React.useCallback(
- async (e?: React.MouseEvent | React.KeyboardEvent | KeyboardEvent) => {
- if (e && app.callbacks.onOpenMedia) e.preventDefault();
- app.callbacks.onOpenMedia?.(app);
- },
- [app]
- );
-
- return {
- onNewProject,
- onSaveProject,
- onSaveProjectAs,
- onOpenProject,
- onOpenMedia,
- };
-}
diff --git a/libs/components/board-draw/src/hooks/use-keyboard-shortcuts.tsx b/libs/components/board-draw/src/hooks/use-keyboard-shortcuts.tsx
deleted file mode 100644
index b52de15e3..000000000
--- a/libs/components/board-draw/src/hooks/use-keyboard-shortcuts.tsx
+++ /dev/null
@@ -1,679 +0,0 @@
-import { AlignStyle, TDShapeType } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { useHotkeys } from 'react-hotkeys-hook';
-import { useFileSystemHandlers } from './use-file-system-handlers';
-import { useTldrawApp } from './use-tldraw-app';
-
-export function useKeyboardShortcuts(ref: React.RefObject) {
- const app = useTldrawApp();
-
- const canHandleEvent = React.useCallback(
- (ignoreMenus = false) => {
- const elm = ref.current;
- if (
- ignoreMenus &&
- (app.isMenuOpen || app.settings.keepStyleMenuOpen)
- )
- return true;
- return (
- elm &&
- (document.activeElement === elm ||
- elm.contains(document.activeElement))
- );
- },
- [ref]
- );
-
- React.useEffect(() => {
- if (!app) return;
-
- const handleCut = (e: ClipboardEvent) => {
- if (!canHandleEvent(true)) return;
-
- if (app.readOnly) {
- app.copy(undefined, undefined, e);
- return;
- }
-
- app.cut(undefined, undefined, e);
- };
-
- const handleCopy = (e: ClipboardEvent) => {
- if (!canHandleEvent(true)) return;
-
- app.copy(undefined, undefined, e);
- };
-
- const handlePaste = (e: ClipboardEvent) => {
- if (!canHandleEvent(true)) return;
- if (app.readOnly) return;
-
- app.paste(undefined, e);
- };
-
- document.addEventListener('cut', handleCut);
- document.addEventListener('copy', handleCopy);
- document.addEventListener('paste', handlePaste);
- return () => {
- document.removeEventListener('cut', handleCut);
- document.removeEventListener('copy', handleCopy);
- document.removeEventListener('paste', handlePaste);
- };
- }, [app]);
-
- /* ---------------------- Tools --------------------- */
-
- useHotkeys(
- 'v,1',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool('select');
- },
- [app, ref.current]
- );
-
- useHotkeys(
- 'd,p,2',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool(TDShapeType.Draw);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'e,3',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool('erase');
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'r,4',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool(TDShapeType.Rectangle);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'o,5',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool(TDShapeType.Ellipse);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'g,6',
- () => {
- if (!canHandleEvent()) return;
- app.selectTool(TDShapeType.Triangle);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'l,7',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool(TDShapeType.Line);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'a,8',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectTool(TDShapeType.Arrow);
- },
- undefined,
- [app]
- );
-
- /* ---------------------- Misc ---------------------- */
-
- // Dark Mode
-
- useHotkeys(
- 'ctrl+shift+d,⌘+shift+d',
- e => {
- if (!canHandleEvent(true)) return;
- app.toggleDarkMode();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- // Focus Mode
-
- useHotkeys(
- 'ctrl+.,⌘+.',
- () => {
- if (!canHandleEvent(true)) return;
- app.toggleFocusMode();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'ctrl+shift+g,⌘+shift+g',
- () => {
- if (!canHandleEvent(true)) return;
- app.toggleGrid();
- },
- undefined,
- [app]
- );
-
- // File System
-
- const {
- onNewProject,
- onOpenProject,
- onSaveProject,
- onSaveProjectAs,
- onOpenMedia,
- } = useFileSystemHandlers();
-
- useHotkeys(
- 'ctrl+n,⌘+n',
- e => {
- if (!canHandleEvent()) return;
-
- onNewProject(e);
- },
- undefined,
- [app]
- );
- useHotkeys(
- 'ctrl+s,⌘+s',
- e => {
- if (!canHandleEvent()) return;
-
- onSaveProject(e);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'ctrl+shift+s,⌘+shift+s',
- e => {
- if (!canHandleEvent()) return;
-
- onSaveProjectAs(e);
- },
- undefined,
- [app]
- );
- useHotkeys(
- 'ctrl+o,⌘+o',
- e => {
- if (!canHandleEvent()) return;
-
- onOpenProject(e);
- },
- undefined,
- [app]
- );
- useHotkeys(
- 'ctrl+u,⌘+u',
- e => {
- if (!canHandleEvent()) return;
- onOpenMedia(e);
- },
- undefined,
- [app]
- );
-
- // Undo Redo
-
- useHotkeys(
- '⌘+z,ctrl+z',
- () => {
- if (!canHandleEvent(true)) return;
-
- if (app.session) {
- app.cancelSession();
- } else {
- app.undo();
- }
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'ctrl+shift+z,⌘+shift+z',
- () => {
- if (!canHandleEvent(true)) return;
-
- if (app.session) {
- app.cancelSession();
- } else {
- app.redo();
- }
- },
- undefined,
- [app]
- );
-
- // Undo Redo
-
- useHotkeys(
- '⌘+u,ctrl+u',
- () => {
- if (!canHandleEvent()) return;
- app.undoSelect();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'ctrl+shift-u,⌘+shift+u',
- () => {
- if (!canHandleEvent()) return;
- app.redoSelect();
- },
- undefined,
- [app]
- );
-
- /* -------------------- Commands -------------------- */
-
- // Camera
-
- useHotkeys(
- 'ctrl+=,⌘+=,ctrl+num_subtract,⌘+num_subtract',
- e => {
- if (!canHandleEvent(true)) return;
- app.zoomIn();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'ctrl+-,⌘+-,ctrl+num_add,⌘+num_add',
- e => {
- if (!canHandleEvent(true)) return;
-
- app.zoomOut();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+0,ctrl+numpad_0,⌘+numpad_0',
- () => {
- if (!canHandleEvent(true)) return;
- app.resetZoom();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+1',
- () => {
- if (!canHandleEvent(true)) return;
- app.zoomToFit();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+2',
- () => {
- if (!canHandleEvent(true)) return;
- app.zoomToSelection();
- },
- undefined,
- [app]
- );
-
- // Duplicate
-
- useHotkeys(
- 'ctrl+d,⌘+d',
- e => {
- if (!canHandleEvent()) return;
-
- app.duplicate();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- // Flip
-
- useHotkeys(
- 'shift+h',
- () => {
- if (!canHandleEvent(true)) return;
- app.flipHorizontal();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+v',
- () => {
- if (!canHandleEvent(true)) return;
- app.flipVertical();
- },
- undefined,
- [app]
- );
-
- // Cancel
-
- useHotkeys(
- 'escape',
- () => {
- if (!canHandleEvent(true)) return;
-
- app.cancel();
- },
- undefined,
- [app]
- );
-
- // Delete
-
- useHotkeys(
- 'backspace,del',
- () => {
- if (!canHandleEvent()) return;
- app.delete();
- },
- undefined,
- [app]
- );
-
- // Select All
-
- useHotkeys(
- '⌘+a,ctrl+a',
- () => {
- if (!canHandleEvent(true)) return;
- app.selectAll();
- },
- undefined,
- [app]
- );
-
- // Nudge
-
- useHotkeys(
- 'up',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([0, -1], false);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'right',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([1, 0], false);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'down',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([0, 1], false);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'left',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([-1, 0], false);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+up',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([0, -1], true);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+right',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([1, 0], true);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+down',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([0, 1], true);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+left',
- () => {
- if (!canHandleEvent()) return;
- app.nudge([-1, 0], true);
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- '⌘+shift+l,ctrl+shift+l',
- () => {
- if (!canHandleEvent()) return;
- app.toggleLocked();
- },
- undefined,
- [app]
- );
-
- // Copy, Cut & Paste
-
- // useHotkeys(
- // '⌘+c,ctrl+c',
- // () => {
- // if (!canHandleEvent()) return
- // app.copy()
- // },
- // undefined,
- // [app]
- // )
-
- useHotkeys(
- '⌘+shift+c,ctrl+shift+c',
- e => {
- if (!canHandleEvent()) return;
-
- app.copySvg();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- // useHotkeys(
- // '⌘+x,ctrl+x',
- // () => {
- // if (!canHandleEvent()) return
- // app.cut()
- // },
- // undefined,
- // [app]
- // )
-
- // useHotkeys(
- // '⌘+v,ctrl+v',
- // () => {
- // if (!canHandleEvent()) return
-
- // app.paste()
- // },
- // undefined,
- // [app]
- // )
-
- // Group & Ungroup
-
- useHotkeys(
- '⌘+g,ctrl+g',
- e => {
- if (!canHandleEvent()) return;
-
- app.group();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- '⌘+shift+g,ctrl+shift+g',
- e => {
- if (!canHandleEvent()) return;
-
- app.ungroup();
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- // Move
-
- useHotkeys(
- '[',
- () => {
- if (!canHandleEvent(true)) return;
- app.moveBackward();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- ']',
- () => {
- if (!canHandleEvent(true)) return;
- app.moveForward();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+[',
- () => {
- if (!canHandleEvent(true)) return;
- app.moveToBack();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'shift+]',
- () => {
- if (!canHandleEvent(true)) return;
- app.moveToFront();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'ctrl+shift+backspace,⌘+shift+backspace',
- e => {
- if (!canHandleEvent()) return;
- if (app.settings.isDebugMode) {
- app.resetDocument();
- }
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- // Text Align
-
- useHotkeys(
- 'alt+command+l,alt+ctrl+l',
- e => {
- if (!canHandleEvent(true)) return;
- app.style({ textAlign: AlignStyle.Start });
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'alt+command+t,alt+ctrl+t',
- e => {
- if (!canHandleEvent(true)) return;
- app.style({ textAlign: AlignStyle.Middle });
- e.preventDefault();
- },
- undefined,
- [app]
- );
-
- useHotkeys(
- 'alt+command+r,alt+ctrl+r',
- e => {
- if (!canHandleEvent(true)) return;
- app.style({ textAlign: AlignStyle.End });
- e.preventDefault();
- },
- undefined,
- [app]
- );
-}
diff --git a/libs/components/board-draw/src/hooks/use-stylesheet.ts b/libs/components/board-draw/src/hooks/use-stylesheet.ts
deleted file mode 100644
index b611e6a64..000000000
--- a/libs/components/board-draw/src/hooks/use-stylesheet.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import * as React from 'react';
-
-const styles = new Map();
-
-const UID = `tldraw-fonts`;
-
-const CSS = `
-@import url('https://fonts.googleapis.com/css2?family=Caveat+Brush&family=Source+Code+Pro&family=Source+Sans+Pro&family=Crimson+Pro&display=block');
-
-@font-face {
- font-family: 'Recursive';
- font-style: normal;
- font-weight: 500;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
- format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
- U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-@font-face {
- font-family: 'Recursive';
- font-style: normal;
- font-weight: 700;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImKsvxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
- format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
- U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-
-@font-face {
- font-family: 'Recursive Mono';
- font-style: normal;
- font-weight: 420;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/recursive/v23/8vI-7wMr0mhh-RQChyHEH06TlXhq_gukbYrFMk1QuAIcyEwG_X-dpEfaE5YaERmK-CImqvTxvU-MXGX2fSqasNfUlTGZnI14ZeY.woff2)
- format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
- U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
-}
-`;
-
-export function useStylesheet() {
- React.useLayoutEffect(() => {
- if (styles.get(UID)) return;
- const style = document.createElement('style');
- style.innerHTML = CSS;
- style.setAttribute('id', UID);
- document.head.appendChild(style);
- styles.set(UID, style);
-
- return () => {
- if (style && document.head.contains(style)) {
- document.head.removeChild(style);
- styles.delete(UID);
- }
- };
- }, []);
-}
diff --git a/libs/components/board-draw/src/hooks/use-tldraw-app.ts b/libs/components/board-draw/src/hooks/use-tldraw-app.ts
deleted file mode 100644
index c96a076a5..000000000
--- a/libs/components/board-draw/src/hooks/use-tldraw-app.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import * as React from 'react';
-
-export const TldrawContext = React.createContext({} as TldrawApp);
-
-export function useTldrawApp() {
- const context = React.useContext(TldrawContext);
- return context;
-}
diff --git a/libs/components/board-draw/src/index.ts b/libs/components/board-draw/src/index.ts
deleted file mode 100644
index aeaf8da12..000000000
--- a/libs/components/board-draw/src/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { useTldrawApp } from './hooks';
-export { Tldraw } from './TlDraw';
diff --git a/libs/components/board-draw/tsconfig.json b/libs/components/board-draw/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/board-draw/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/board-draw/tsconfig.lib.json b/libs/components/board-draw/tsconfig.lib.json
deleted file mode 100644
index 6a4378740..000000000
--- a/libs/components/board-draw/tsconfig.lib.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "jest.config.ts",
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/board-draw/tsconfig.spec.json b/libs/components/board-draw/tsconfig.spec.json
deleted file mode 100644
index 24b3ac659..000000000
--- a/libs/components/board-draw/tsconfig.spec.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "jest.config.ts",
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/board-sessions/.babelrc b/libs/components/board-sessions/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/board-sessions/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/board-sessions/.eslintrc.json b/libs/components/board-sessions/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/board-sessions/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/board-sessions/README.md b/libs/components/board-sessions/README.md
deleted file mode 100644
index dcee10cad..000000000
--- a/libs/components/board-sessions/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-board-sessions
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test components-board-sessions` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/board-sessions/jest.config.ts b/libs/components/board-sessions/jest.config.ts
deleted file mode 100644
index e1555360b..000000000
--- a/libs/components/board-sessions/jest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'components-board-sessions',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/board-sessions',
-};
diff --git a/libs/components/board-sessions/package.json b/libs/components/board-sessions/package.json
deleted file mode 100644
index b4e062da0..000000000
--- a/libs/components/board-sessions/package.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "name": "@toeverything/components/board-sessions",
- "version": "0.0.1",
- "license": "MIT",
- "dependencies": {
- "@tldraw/core": "^1.12.0",
- "@tldraw/intersect": "^1.7.1",
- "@tldraw/vec": "^1.7.0"
- }
-}
diff --git a/libs/components/board-sessions/project.json b/libs/components/board-sessions/project.json
deleted file mode 100644
index 69affe040..000000000
--- a/libs/components/board-sessions/project.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/components/board-sessions/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/board-sessions",
- "tsConfig": "libs/components/board-sessions/tsconfig.lib.json",
- "project": "libs/components/board-sessions/package.json",
- "entryFile": "libs/components/board-sessions/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/board-sessions/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/board-sessions/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/board-sessions"],
- "options": {
- "jestConfig": "libs/components/board-sessions/jest.config.ts",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/board-sessions/src/arrow-session.ts b/libs/components/board-sessions/src/arrow-session.ts
deleted file mode 100644
index bb334c4ee..000000000
--- a/libs/components/board-sessions/src/arrow-session.ts
+++ /dev/null
@@ -1,581 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import { shapeUtils } from '@toeverything/components/board-shapes';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { deepCopy, TLDR } from '@toeverything/components/board-state';
-import {
- ArrowBinding,
- ArrowShape,
- SessionType,
- TDBinding,
- TDShape,
- TDShapeType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class ArrowSession extends BaseSession {
- type = SessionType.Arrow;
- performanceMode: undefined;
- status = TDStatus.TranslatingHandle;
- newStartBindingId = Utils.uniqueId();
- draggedBindingId = Utils.uniqueId();
- didBind = false;
- initialShape: ArrowShape;
- handleId: 'start' | 'end';
- bindableShapeIds: string[];
- initialBinding?: TDBinding;
- startBindingShapeId?: string;
- isCreate: boolean;
-
- constructor(
- app: TldrawApp,
- shapeId: string,
- handleId: 'start' | 'end',
- isCreate = false
- ) {
- super(app);
-
- this.isCreate = isCreate;
-
- const { currentPageId } = app.state.appState;
-
- const page = app.state.document.pages[currentPageId];
-
- this.handleId = handleId;
-
- this.initialShape = deepCopy(page.shapes[shapeId] as ArrowShape);
-
- this.bindableShapeIds = TLDR.get_bindable_shape_ids(app.state).filter(
- id =>
- !(
- id === this.initialShape.id ||
- id === this.initialShape.parentId
- )
- );
-
- // TODO: find out why this the oppositeHandleBindingId is sometimes missing
- const oppositeHandleBindingId =
- this.initialShape.handles[handleId === 'start' ? 'end' : 'start']
- ?.bindingId;
-
- if (oppositeHandleBindingId) {
- const oppositeToId = page.bindings[oppositeHandleBindingId]?.toId;
- if (oppositeToId) {
- this.bindableShapeIds = this.bindableShapeIds.filter(
- id => id !== oppositeToId
- );
- }
- }
-
- const { originPoint } = this.app;
-
- if (this.isCreate) {
- // If we're creating a new shape, should we bind its first point?
- // The method may return undefined, which is correct if there is no
- // bindable shape under the pointer.
- this.startBindingShapeId = this.bindableShapeIds
- .map(id => page.shapes[id])
- .filter(shape =>
- Utils.pointInBounds(
- originPoint,
- TLDR.get_shape_util(shape).getBounds(shape)
- )
- )
- .sort((a, b) => {
- // TODO - We should be smarter here, what's the right logic?
- return b.childIndex - a.childIndex;
- })[0]?.id;
-
- if (this.startBindingShapeId) {
- this.bindableShapeIds.splice(
- this.bindableShapeIds.indexOf(this.startBindingShapeId),
- 1
- );
- }
- } else {
- // If we're editing an existing line, is there a binding already
- // for the dragging handle?
- const initialBindingId =
- this.initialShape.handles[this.handleId].bindingId;
-
- if (initialBindingId) {
- this.initialBinding = page.bindings[initialBindingId];
- } else {
- // If not, explicitly set this handle to undefined, so that it gets deleted on undo
- this.initialShape.handles[this.handleId].bindingId = undefined;
- }
- }
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const { initialShape } = this;
- const {
- currentPoint,
- shiftKey,
- altKey,
- metaKey,
- currentGrid,
- settings: { showGrid },
- } = this.app;
-
- const shape = this.app.getShape(initialShape.id);
-
- if (shape.isLocked) return;
-
- const { handles } = initialShape;
-
- const handleId = this.handleId as keyof typeof handles;
- // If the handle can bind, then we need to search bindable shapes for
- // a binding.
- if (!handles[handleId].canBind) return;
-
- // Find the delta (in shape space)
- let delta = Vec.sub(
- currentPoint,
- Vec.add(handles[handleId].point, initialShape.point)
- );
-
- if (shiftKey) {
- const A = altKey
- ? Vec.med(handles.start.point, handles.end.point)
- : handles[handleId === 'start' ? 'end' : 'start'].point;
- const B = handles[handleId].point;
- const C = Vec.add(B, delta);
-
- const angle = Vec.angle(A, C);
-
- const adjusted = Vec.rotWith(
- C,
- A,
- Utils.snapAngleToSegments(angle, 24) - angle
- );
-
- delta = Vec.add(delta, Vec.sub(adjusted, C));
- }
-
- const nextPoint = Vec.add(handles[handleId].point, delta);
-
- const handleChanges: Partial = {
- [handleId]: {
- ...handles[handleId],
- point: showGrid
- ? Vec.snap(nextPoint, currentGrid)
- : Vec.toFixed(nextPoint),
- bindingId: undefined,
- },
- };
-
- if (altKey) {
- // If the user is holding alt key, apply the inverse delta
- // to the oppoosite handle.
- const oppositeHandleId = handleId === 'start' ? 'end' : 'start';
-
- const nextPoint = Vec.sub(handles[oppositeHandleId].point, delta);
-
- handleChanges[oppositeHandleId] = {
- ...handles[oppositeHandleId],
- point: showGrid
- ? Vec.snap(nextPoint, currentGrid)
- : Vec.toFixed(nextPoint),
- bindingId: undefined,
- };
- }
-
- const utils = shapeUtils[TDShapeType.Arrow];
- const handleChange = utils.onHandleChange?.(
- initialShape,
- handleChanges
- );
-
- // If the handle changed produced no change, bail here
- if (!handleChange) return;
-
- // If nothing changes, we want these to be the same object reference as
- // before. If it does change, we'll redefine this later on. And if we've
- // made it this far, the shape should be a new object reference that
- // incorporates the changes we've made due to the handle movement.
- const next: {
- shape: ArrowShape;
- bindings: Record;
- } = {
- shape: Utils.deepMerge(shape, handleChange),
- bindings: {},
- };
-
- let draggedBinding: ArrowBinding | undefined;
-
- const draggingHandle = next.shape.handles[this.handleId];
-
- const oppositeHandle =
- next.shape.handles[this.handleId === 'start' ? 'end' : 'start'];
-
- // START BINDING
- // If we have a start binding shape id, the recompute the binding
- // point based on the current end handle position
- if (this.startBindingShapeId) {
- let nextStartBinding: ArrowBinding | undefined;
-
- const startTarget = this.app.page.shapes[this.startBindingShapeId];
-
- const startTargetUtils = TLDR.get_shape_util(startTarget);
-
- const center = startTargetUtils.getCenter(startTarget);
-
- const startHandle = next.shape.handles.start;
-
- const endHandle = next.shape.handles.end;
-
- const rayPoint = Vec.add(startHandle.point, next.shape.point);
-
- if (Vec.isEqual(rayPoint, center)) rayPoint[1]++; // Fix bug where ray and center are identical
-
- const rayOrigin = center;
-
- const isInsideShape = startTargetUtils.hitTestPoint(
- startTarget,
- currentPoint
- );
-
- const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin));
-
- const hasStartBinding =
- this.app.getBinding(this.newStartBindingId) !== undefined;
-
- // Don't bind the start handle if both handles are inside of the target shape.
- if (
- !metaKey &&
- !startTargetUtils.hitTestPoint(
- startTarget,
- Vec.add(next.shape.point, endHandle.point)
- )
- ) {
- nextStartBinding = this.find_binding_point(
- shape,
- startTarget,
- 'start',
- this.newStartBindingId,
- center,
- rayOrigin,
- rayDirection,
- isInsideShape
- );
- }
-
- if (nextStartBinding && !hasStartBinding) {
- // Bind the arrow's start handle to the start target
- this.didBind = true;
-
- next.bindings[this.newStartBindingId] = nextStartBinding;
-
- next.shape = Utils.deepMerge(next.shape, {
- handles: {
- start: {
- bindingId: nextStartBinding.id,
- },
- },
- });
- } else if (!nextStartBinding && hasStartBinding) {
- // Remove the start binding
- this.didBind = false;
-
- next.bindings[this.newStartBindingId] = undefined;
-
- next.shape = Utils.deepMerge(initialShape, {
- handles: {
- start: {
- bindingId: undefined,
- },
- },
- });
- }
- }
-
- // DRAGGED POINT BINDING
- if (!metaKey) {
- const rayOrigin = Vec.add(oppositeHandle.point, next.shape.point);
-
- const rayPoint = Vec.add(draggingHandle.point, next.shape.point);
-
- const rayDirection = Vec.uni(Vec.sub(rayPoint, rayOrigin));
-
- const startPoint = Vec.add(
- next.shape.point!,
- next.shape.handles!.start.point!
- );
-
- const endPoint = Vec.add(
- next.shape.point!,
- next.shape.handles!.end.point!
- );
-
- const targets = this.bindableShapeIds
- .map(id => this.app.page.shapes[id])
- .sort((a, b) => b.childIndex - a.childIndex)
- .filter(shape => {
- const utils = TLDR.get_shape_util(shape);
- return ![startPoint, endPoint].every(point =>
- utils.hitTestPoint(shape, point)
- );
- });
-
- for (const target of targets) {
- draggedBinding = this.find_binding_point(
- shape,
- target,
- this.handleId,
- this.draggedBindingId,
- rayPoint,
- rayOrigin,
- rayDirection,
- altKey
- );
-
- if (draggedBinding) break;
- }
- }
- if (draggedBinding) {
- // Create the dragged point binding
- this.didBind = true;
-
- next.bindings[this.draggedBindingId] = draggedBinding;
-
- next.shape = Utils.deepMerge(next.shape, {
- handles: {
- [this.handleId]: {
- bindingId: this.draggedBindingId,
- },
- },
- });
- } else {
- // Remove the dragging point binding
- this.didBind = this.didBind || false;
-
- const currentBindingId = shape.handles[this.handleId].bindingId;
-
- if (currentBindingId !== undefined) {
- next.bindings[currentBindingId] = undefined;
-
- next.shape = Utils.deepMerge(next.shape, {
- handles: {
- [this.handleId]: {
- bindingId: undefined,
- },
- },
- });
- }
- }
-
- const change = TLDR.get_shape_util(
- next.shape
- ).onHandleChange?.(next.shape, next.shape.handles);
-
- return {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [shape.id]: { ...next.shape, ...(change ?? {}) },
- },
- bindings: next.bindings,
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- bindingId: next.shape.handles[handleId].bindingId,
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- initialShape,
- initialBinding,
- newStartBindingId,
- draggedBindingId,
- } = this;
-
- const currentShape = TLDR.on_session_complete(
- this.app.page.shapes[initialShape.id]
- ) as ArrowShape;
-
- const isDeleting =
- this.isCreate ||
- Vec.dist(
- currentShape.handles.start.point,
- currentShape.handles.end.point
- ) < 4;
-
- const afterBindings: Record = {};
-
- afterBindings[draggedBindingId] = undefined;
-
- if (initialBinding) {
- afterBindings[initialBinding.id] = isDeleting
- ? undefined
- : initialBinding;
- }
-
- if (newStartBindingId) {
- afterBindings[newStartBindingId] = undefined;
- }
-
- return {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [initialShape.id]: isDeleting
- ? undefined
- : initialShape,
- },
- bindings: afterBindings,
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: isDeleting ? [] : [initialShape.id],
- bindingId: undefined,
- hoveredId: undefined,
- editingId: undefined,
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- initialShape,
- initialBinding,
- newStartBindingId,
- startBindingShapeId,
- handleId,
- } = this;
-
- const currentShape = TLDR.on_session_complete(
- this.app.page.shapes[initialShape.id]
- ) as ArrowShape;
-
- const currentBindingId = currentShape.handles[handleId].bindingId;
-
- const length = Vec.dist(
- currentShape.handles.start.point,
- currentShape.handles.end.point
- );
-
- if (!(currentBindingId || initialBinding) && length < 4)
- return this.cancel();
-
- const beforeBindings: Partial> = {};
-
- const afterBindings: Partial> = {};
-
- if (initialBinding) {
- beforeBindings[initialBinding.id] = this.isCreate
- ? undefined
- : initialBinding;
- afterBindings[initialBinding.id] = undefined;
- }
-
- if (currentBindingId) {
- beforeBindings[currentBindingId] = undefined;
- afterBindings[currentBindingId] =
- this.app.page.bindings[currentBindingId];
- }
-
- if (startBindingShapeId) {
- beforeBindings[newStartBindingId] = undefined;
- afterBindings[newStartBindingId] =
- this.app.page.bindings[newStartBindingId];
- }
-
- return {
- id: 'arrow',
- before: {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [initialShape.id]: this.isCreate
- ? undefined
- : initialShape,
- },
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: this.isCreate ? [] : [initialShape.id],
- bindingId: undefined,
- hoveredId: undefined,
- editingId: undefined,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [initialShape.id]: currentShape,
- },
- bindings: afterBindings,
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: [initialShape.id],
- bindingId: undefined,
- hoveredId: undefined,
- editingId: undefined,
- },
- },
- },
- },
- };
- };
-
- private find_binding_point = (
- shape: ArrowShape,
- target: TDShape,
- handleId: 'start' | 'end',
- bindingId: string,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- const util = TLDR.get_shape_util(target.type);
-
- const bindingPoint = util.getBindingPoint(
- target,
- shape,
- point, // fix dead center bug
- origin,
- direction,
- bindAnywhere
- );
-
- // Not all shapes will produce a binding point
- if (!bindingPoint) return;
-
- return {
- id: bindingId,
- type: 'arrow',
- fromId: shape.id,
- toId: target.id,
- handleId: handleId,
- point: Vec.toFixed(bindingPoint.point),
- distance: bindingPoint.distance,
- };
- };
-}
diff --git a/libs/components/board-sessions/src/base-session.ts b/libs/components/board-sessions/src/base-session.ts
deleted file mode 100644
index a14fa663f..000000000
--- a/libs/components/board-sessions/src/base-session.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { BaseSessionType } from '@toeverything/components/board-types';
-
-export abstract class BaseSession extends BaseSessionType {
- app: TldrawApp;
- constructor(app: TldrawApp) {
- super();
- this.app = app;
- }
-}
diff --git a/libs/components/board-sessions/src/brush-session.ts b/libs/components/board-sessions/src/brush-session.ts
deleted file mode 100644
index 981e2149d..000000000
--- a/libs/components/board-sessions/src/brush-session.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { TLBounds, Utils } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import {
- SessionType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class BrushSession extends BaseSession {
- type = SessionType.Brush;
- performanceMode: undefined;
- status = TDStatus.Brushing;
- initialSelectedIds: Set;
- shapesToTest: {
- id: string;
- bounds: TLBounds;
- selectId: string;
- }[];
-
- constructor(app: TldrawApp) {
- super(app);
- const { currentPageId } = app;
- this.initialSelectedIds = new Set(this.app.selectedIds);
- this.shapesToTest = this.app.shapes
- .filter(
- shape =>
- !(
- shape.isLocked ||
- shape.isHidden ||
- shape.parentId !== currentPageId ||
- this.initialSelectedIds.has(shape.id) ||
- this.initialSelectedIds.has(shape.parentId)
- )
- )
- .map(shape => ({
- id: shape.id,
- bounds: this.app.getShapeUtil(shape).getBounds(shape),
- selectId: shape.id, //TLDR.getTopParentId(data, shape.id, currentPageId),
- }));
-
- this.update();
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const {
- initialSelectedIds,
- shapesToTest,
- app: { metaKey, settings, originPoint, currentPoint },
- } = this;
-
- // Create a bounding box between the origin and the new point
- const brush = Utils.getBoundsFromPoints([originPoint, currentPoint]);
-
- // Decide weather to select by intersecting or by overlapping
- // Using a xor to revers the behaviour if the ctrl key is pressed
- // Do it only if the user choose to enable cad like selection
- const selectByContain = settings.isCadSelectMode
- ? !metaKey && originPoint[0] < currentPoint[0]
- : metaKey;
-
- // Find ids of brushed shapes
- const hits = new Set();
-
- const selectedIds = new Set(initialSelectedIds);
-
- shapesToTest.forEach(({ id, selectId }) => {
- const shape = this.app.getShape(id);
- if (!hits.has(selectId)) {
- const util = this.app.getShapeUtil(shape);
- if (
- selectByContain
- ? Utils.boundsContain(brush, util.getBounds(shape))
- : util.hitTestBounds(shape, brush)
- ) {
- hits.add(selectId);
-
- // When brushing a shape, select its top group parent.
- if (!selectedIds.has(selectId)) {
- selectedIds.add(selectId);
- }
- } else if (selectedIds.has(selectId)) {
- selectedIds.delete(selectId);
- }
- }
- });
-
- const currentSelectedIds = this.app.selectedIds;
-
- const didChange =
- selectedIds.size !== currentSelectedIds.length ||
- currentSelectedIds.some(id => !selectedIds.has(id));
-
- const afterSelectedIds = didChange
- ? Array.from(selectedIds.values())
- : currentSelectedIds;
-
- return {
- appState: {
- selectByContain,
- },
- document: {
- pageStates: {
- [this.app.currentPageId]: {
- brush,
- selectedIds: afterSelectedIds,
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- return {
- appState: {
- selectByContain: false,
- },
- document: {
- pageStates: {
- [this.app.currentPageId]: {
- brush: null,
- selectedIds: Array.from(
- this.initialSelectedIds.values()
- ),
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- return {
- appState: {
- selectByContain: false,
- },
- document: {
- pageStates: {
- [this.app.currentPageId]: {
- brush: null,
- selectedIds: [...this.app.selectedIds],
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/draw-session.ts b/libs/components/board-sessions/src/draw-session.ts
deleted file mode 100644
index 1cec1392a..000000000
--- a/libs/components/board-sessions/src/draw-session.ts
+++ /dev/null
@@ -1,305 +0,0 @@
-import { Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import {
- DrawShape,
- SessionType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class DrawSession extends BaseSession {
- type = SessionType.Draw;
- performanceMode: undefined;
- status = TDStatus.Creating;
- topLeft: number[];
- points: number[][];
- initialShape: DrawShape;
- lastAdjustedPoint: number[];
- shiftedPoints: number[][] = [];
- shapeId: string;
- isLocked?: boolean;
- isExtending: boolean;
- lockedDirection?: 'horizontal' | 'vertical';
-
- constructor(app: TldrawApp, id: string) {
- super(app);
- const { originPoint } = this.app;
- this.shapeId = id;
- this.initialShape = this.app.getShape(id);
- this.topLeft = [...this.initialShape.point];
- const currentPoint = [0, 0, originPoint[2] ?? 0.5];
- const delta = Vec.sub(originPoint, this.topLeft);
- const initialPoints = this.initialShape.points.map(pt =>
- Vec.sub(pt, delta).concat(pt[2])
- );
- this.isExtending = initialPoints.length > 0;
- const newPoints: number[][] = [];
- if (this.isExtending) {
- const prevPoint = initialPoints[initialPoints.length - 1];
- newPoints.push(prevPoint, prevPoint);
- // Continuing with shift
- const len = Math.ceil(Vec.dist(prevPoint, currentPoint) / 16);
- for (let i = 0; i < len; i++) {
- const t = i / (len - 1);
- newPoints.push(
- Vec.lrp(prevPoint, currentPoint, t).concat(prevPoint[2])
- );
- }
- } else {
- newPoints.push(currentPoint);
- }
- // Add a first point but don't update the shape yet. We'll update
- // when the draw session ends; if the user hasn't added additional
- // points, this single point will be interpreted as a "dot" shape.
- this.points = [...initialPoints, ...newPoints];
- this.shiftedPoints = this.points.map(pt =>
- Vec.add(pt, delta).concat(pt[2])
- );
- this.lastAdjustedPoint = this.points[this.points.length - 1];
- }
-
- start = () => {
- const currentPoint = this.app.originPoint;
- const newAdjustedPoint = [0, 0, currentPoint[2] ?? 0.5];
- // Add the new adjusted point to the points array
- this.points.push(newAdjustedPoint);
- const topLeft = [
- Math.min(this.topLeft[0], currentPoint[0]),
- Math.min(this.topLeft[1], currentPoint[1]),
- ];
- const delta = Vec.sub(topLeft, currentPoint);
- this.topLeft = topLeft;
- this.shiftedPoints = this.points.map(pt =>
- Vec.toFixed(Vec.sub(pt, delta)).concat(pt[2])
- );
-
- return {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [this.shapeId]: {
- point: this.topLeft,
- points: this.shiftedPoints,
- },
- },
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: [this.shapeId],
- },
- },
- },
- };
- };
-
- update = (): TldrawPatch | undefined => {
- const { shapeId } = this;
- const { currentPoint, originPoint, shiftKey } = this.app;
-
- // Even if we're not locked yet, we base the future locking direction
- // on the first dimension to reach a threshold, or the bigger dimension
- // once one or both dimensions have reached the threshold.
- if (!this.lockedDirection && this.points.length > 1) {
- const bounds = Utils.getBoundsFromPoints(this.points);
- if (bounds.width > 8 || bounds.height > 8) {
- this.lockedDirection =
- bounds.width > bounds.height ? 'horizontal' : 'vertical';
- }
- }
-
- // Drawing while holding shift will "lock" the pen to either the
- // x or y axis, depending on the locking direction.
- if (shiftKey) {
- if (!this.isLocked && this.points.length > 2) {
- // If we're locking before knowing what direction we're in, set it
- // early based on the bigger dimension.
- if (!this.lockedDirection) {
- const bounds = Utils.getBoundsFromPoints(this.points);
- this.lockedDirection =
- bounds.width > bounds.height
- ? 'horizontal'
- : 'vertical';
- }
-
- this.isLocked = true;
- // Start locking
- const returning = [...this.lastAdjustedPoint];
-
- if (this.lockedDirection === 'vertical') {
- returning[0] = 0;
- } else {
- returning[1] = 0;
- }
-
- this.points.push(returning.concat(currentPoint[2]));
- }
- } else if (this.isLocked) {
- this.isLocked = false;
- }
-
- if (this.isLocked) {
- if (this.lockedDirection === 'vertical') {
- currentPoint[0] = originPoint[0];
- } else {
- currentPoint[1] = originPoint[1];
- }
- }
-
- const change = this.addPoint(currentPoint);
-
- if (!change) {
- return;
- }
-
- return {
- document: {
- pages: {
- [this.app.currentPageId]: {
- shapes: {
- [shapeId]: change,
- },
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: [shapeId],
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const { shapeId } = this;
- const pageId = this.app.currentPageId;
-
- return {
- document: {
- pages: {
- [pageId]: {
- shapes: {
- [shapeId]: this.isExtending
- ? this.initialShape
- : undefined,
- },
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: [],
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const { shapeId } = this;
- const pageId = this.app.currentPageId;
- const shape = this.app.getShape(shapeId);
- return {
- id: 'create_draw',
- before: {
- document: {
- pages: {
- [pageId]: {
- shapes: {
- [shapeId]: this.isExtending
- ? this.initialShape
- : undefined,
- },
- },
- },
- pageStates: {
- [pageId]: {
- selectedIds: [],
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [pageId]: {
- shapes: {
- [shapeId]: {
- ...shape,
- point: Vec.toFixed(shape.point),
- points: shape.points.map(pt =>
- Vec.toFixed(pt)
- ),
- isComplete: true,
- },
- },
- },
- },
- pageStates: {
- [this.app.currentPageId]: {
- selectedIds: [],
- },
- },
- },
- },
- };
- };
-
- addPoint = (currentPoint: number[]) => {
- const { originPoint } = this.app;
- // The new adjusted point
- const newAdjustedPoint = Vec.toFixed(
- Vec.sub(currentPoint, originPoint)
- ).concat(currentPoint[2]);
-
- // Don't add duplicate points.
- if (Vec.isEqual(this.lastAdjustedPoint, newAdjustedPoint)) return;
-
- // Add the new adjusted point to the points array
- this.points.push(newAdjustedPoint);
-
- // The new adjusted point is now the previous adjusted point.
- this.lastAdjustedPoint = newAdjustedPoint;
-
- // Does the input point create a new top left?
- const prevTopLeft = [...this.topLeft];
-
- const topLeft = [
- Math.min(this.topLeft[0], currentPoint[0]),
- Math.min(this.topLeft[1], currentPoint[1]),
- ];
-
- const delta = Vec.sub(topLeft, originPoint);
-
- // Time to shift some points!
- let points: number[][];
-
- if (prevTopLeft[0] !== topLeft[0] || prevTopLeft[1] !== topLeft[1]) {
- this.topLeft = topLeft;
- // If we have a new top left, then we need to iterate through
- // the "unshifted" points array and shift them based on the
- // offset between the new top left and the original top left.
- points = this.points.map(pt =>
- Vec.toFixed(Vec.sub(pt, delta)).concat(pt[2])
- );
- } else {
- // If the new top left is the same as the previous top left,
- // we don't need to shift anything: we just shift the new point
- // and add it to the shifted points array.
- points = [
- ...this.shiftedPoints,
- Vec.sub(newAdjustedPoint, delta).concat(newAdjustedPoint[2]),
- ];
- }
-
- this.shiftedPoints = points;
-
- return {
- point: this.topLeft,
- points,
- };
- };
-}
diff --git a/libs/components/board-sessions/src/erase-session.ts b/libs/components/board-sessions/src/erase-session.ts
deleted file mode 100644
index 2f7121dd1..000000000
--- a/libs/components/board-sessions/src/erase-session.ts
+++ /dev/null
@@ -1,274 +0,0 @@
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import {
- PagePartial,
- SessionType,
- TDBinding,
- TDShape,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class EraseSession extends BaseSession {
- type = SessionType.Draw;
- performanceMode: undefined;
- status = TDStatus.Creating;
- isLocked?: boolean;
- lockedDirection?: 'horizontal' | 'vertical';
- erasedShapes = new Set();
- erasedBindings = new Set();
- initialSelectedShapes: TDShape[];
- erasableShapes: Set;
- prevPoint: number[];
-
- constructor(app: TldrawApp) {
- super(app);
- this.prevPoint = [...app.originPoint];
- this.initialSelectedShapes = this.app.selectedIds.map(id =>
- this.app.getShape(id)
- );
- this.erasableShapes = new Set(
- this.app.shapes.filter(shape => !shape.isLocked)
- );
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const { page, shiftKey, originPoint, currentPoint } = this.app;
-
- if (shiftKey) {
- if (!this.isLocked && Vec.dist(originPoint, currentPoint) > 4) {
- // If we're locking before knowing what direction we're in, set it
- // early based on the bigger dimension.
- if (!this.lockedDirection) {
- const delta = Vec.sub(currentPoint, originPoint);
- this.lockedDirection =
- delta[0] > delta[1] ? 'horizontal' : 'vertical';
- }
-
- this.isLocked = true;
- }
- } else if (this.isLocked) {
- this.isLocked = false;
- }
-
- if (this.isLocked) {
- if (this.lockedDirection === 'vertical') {
- currentPoint[0] = originPoint[0];
- } else {
- currentPoint[1] = originPoint[1];
- }
- }
-
- const newPoint = Vec.toFixed(
- Vec.add(originPoint, Vec.sub(currentPoint, originPoint))
- );
-
- const deletedShapeIds = new Set([]);
-
- this.erasableShapes.forEach(shape => {
- if (this.erasedShapes.has(shape)) return;
- if (
- this.app
- .getShapeUtil(shape)
- .hitTestLineSegment(shape, this.prevPoint, newPoint)
- ) {
- this.erasedShapes.add(shape);
- deletedShapeIds.add(shape.id);
-
- if (shape.children !== undefined) {
- for (const childId of shape.children) {
- this.erasedShapes.add(this.app.getShape(childId));
- deletedShapeIds.add(childId);
- }
- }
- }
- });
-
- // Erase bindings that reference deleted shapes
-
- Object.values(page.bindings).forEach(binding => {
- for (const id of [binding.toId, binding.fromId]) {
- if (deletedShapeIds.has(id)) {
- this.erasedBindings.add(binding);
- }
- }
- });
-
- this.erasedShapes.forEach(shape => {
- // Has the shape been deleted? If so, pull it from the list.
- if (!this.app.getShape(shape.id)) {
- this.erasedShapes.delete(shape);
- this.erasableShapes.delete(shape);
- deletedShapeIds.delete(shape.id);
- }
- });
-
- const erasedShapes = Array.from(this.erasedShapes.values());
-
- this.prevPoint = newPoint;
-
- return {
- document: {
- pages: {
- [page.id]: {
- shapes: Object.fromEntries(
- erasedShapes.map(shape => [
- shape.id,
- { isGhost: true },
- ])
- ),
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const { page } = this.app;
-
- this.erasedShapes.forEach(shape => {
- if (!this.app.getShape(shape.id)) {
- this.erasedShapes.delete(shape);
- this.erasableShapes.delete(shape);
- }
- });
-
- const erasedShapes = Array.from(this.erasedShapes.values());
-
- return {
- document: {
- pages: {
- [page.id]: {
- shapes: Object.fromEntries(
- erasedShapes.map(shape => [
- shape.id,
- { isGhost: false },
- ])
- ),
- },
- },
- pageStates: {
- [page.id]: {
- selectedIds: this.initialSelectedShapes.map(
- shape => shape.id
- ),
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const { page } = this.app;
-
- this.erasedShapes.forEach(shape => {
- if (!this.app.getShape(shape.id)) {
- this.erasedShapes.delete(shape);
- this.erasableShapes.delete(shape);
- }
- });
-
- this.erasedBindings.forEach(binding => {
- if (!this.app.getBinding(binding.id)) {
- this.erasedBindings.delete(binding);
- }
- });
-
- const erasedShapes = Array.from(this.erasedShapes.values());
- const erasedBindings = Array.from(this.erasedBindings.values());
- const erasedShapeIds = erasedShapes.map(shape => shape.id);
- const erasedBindingIds = erasedBindings.map(binding => binding.id);
-
- const before: PagePartial = {
- shapes: Object.fromEntries(
- erasedShapes.map(shape => [shape.id, shape])
- ),
- bindings: Object.fromEntries(
- erasedBindings.map(binding => [binding.id, binding])
- ),
- };
-
- const after: PagePartial = {
- shapes: Object.fromEntries(
- erasedShapes.map(shape => [shape.id, undefined])
- ),
- bindings: Object.fromEntries(
- erasedBindings.map(binding => [binding.id, undefined])
- ),
- };
-
- // Remove references on any shape's handles to any deleted bindings
- this.app.shapes.forEach(shape => {
- if (shape.handles && !after.shapes[shape.id]) {
- Object.values(shape.handles).forEach(handle => {
- if (
- handle.bindingId &&
- erasedBindingIds.includes(handle.bindingId)
- ) {
- // Save the binding reference in the before patch
- before.shapes[shape.id] = {
- ...before.shapes[shape.id],
- handles: {
- ...before.shapes[shape.id]?.handles,
- [handle.id]: handle,
- },
- };
-
- // Save the binding reference in the before patch
- if (!erasedShapeIds.includes(shape.id)) {
- after.shapes[shape.id] = {
- ...after.shapes[shape.id],
- handles: {
- ...after.shapes[shape.id]?.handles,
- [handle.id]: {
- ...handle,
- bindingId: undefined,
- },
- },
- };
- }
- }
- });
- }
- });
-
- return {
- id: 'erase',
- before: {
- document: {
- pages: {
- [page.id]: before,
- },
- pageStates: {
- [page.id]: {
- selectedIds: this.initialSelectedShapes
- .filter(shape => !!this.app.getShape(shape.id))
- .map(shape => shape.id),
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [page.id]: after,
- },
- pageStates: {
- [page.id]: {
- selectedIds: this.initialSelectedShapes
- .filter(shape => !!this.app.getShape(shape.id))
- .filter(
- shape => !erasedShapeIds.includes(shape.id)
- )
- .map(shape => shape.id),
- },
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/grid-session.ts b/libs/components/board-sessions/src/grid-session.ts
deleted file mode 100644
index 848faead3..000000000
--- a/libs/components/board-sessions/src/grid-session.ts
+++ /dev/null
@@ -1,269 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { TLBounds, TLPageState, Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import {
- Patch,
- SessionType,
- TDShape,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class GridSession extends BaseSession {
- type = SessionType.Grid;
- performanceMode: undefined;
- status = TDStatus.Translating;
- shape: TDShape;
- bounds: TLBounds;
- initialSelectedIds: string[];
- initialSiblings?: string[];
- grid: Record = {};
- columns = 1;
- rows = 1;
- isCopying = false;
-
- constructor(app: TldrawApp, id: string) {
- super(app);
- this.shape = this.app.getShape(id);
- this.grid['0_0'] = this.shape.id;
- this.bounds = this.app.getShapeBounds(id);
- this.initialSelectedIds = [...this.app.selectedIds];
- if (this.shape.parentId !== this.app.currentPageId) {
- this.initialSiblings = this.app
- .getShape(this.shape.parentId)
- .children?.filter(id => id !== this.shape.id);
- }
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const { currentPageId, altKey, shiftKey, currentPoint } = this.app;
-
- const nextShapes: Patch> = {};
-
- const nextPageState: Patch = {};
-
- const center = Utils.getBoundsCenter(this.bounds);
-
- const offset = Vec.sub(currentPoint, center);
-
- if (shiftKey) {
- if (Math.abs(offset[0]) < Math.abs(offset[1])) {
- offset[0] = 0;
- } else {
- offset[1] = 0;
- }
- }
- // use the distance from center to determine the grid
-
- const gapX = this.bounds.width + 32;
- const gapY = this.bounds.height + 32;
-
- const columns = Math.ceil(offset[0] / gapX);
- const rows = Math.ceil(offset[1] / gapY);
-
- const minX = Math.min(columns, 0);
- const minY = Math.min(rows, 0);
- const maxX = Math.max(columns, 1);
- const maxY = Math.max(rows, 1);
-
- const inGrid = new Set();
-
- const isCopying = altKey;
-
- if (isCopying !== this.isCopying) {
- // Recreate shapes copying
- Object.values(this.grid)
- .filter(id => id !== this.shape.id)
- .forEach(id => (nextShapes[id] = undefined));
-
- this.grid = { '0_0': this.shape.id };
-
- this.isCopying = isCopying;
- }
-
- // Go through grid, adding items in positions
- // that aren't already filled.
- for (let x = minX; x < maxX; x++) {
- for (let y = minY; y < maxY; y++) {
- const position = `${x}_${y}`;
-
- inGrid.add(position);
-
- if (this.grid[position]) continue;
-
- if (x === 0 && y === 0) continue;
-
- const clone = this.get_clone(
- Vec.add(this.shape.point, [x * gapX, y * gapY]),
- isCopying
- );
-
- nextShapes[clone.id] = clone;
-
- this.grid[position] = clone.id;
- }
- }
-
- // Remove any other items from the grid
- Object.entries(this.grid).forEach(([position, id]) => {
- if (!inGrid.has(position)) {
- nextShapes[id] = undefined;
- delete this.grid[position];
- }
- });
-
- if (Object.values(nextShapes).length === 0) return;
-
- // Add shapes to parent id
- if (this.initialSiblings) {
- nextShapes[this.shape.parentId] = {
- children: [
- ...this.initialSiblings,
- ...Object.values(this.grid),
- ],
- };
- }
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes: nextShapes,
- },
- },
- pageStates: {
- [currentPageId]: nextPageState,
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const { currentPageId } = this.app;
- const nextShapes: Record | undefined> = {};
-
- // Delete clones
- Object.values(this.grid).forEach(id => {
- nextShapes[id] = undefined;
- // TODO: Remove from parent if grouped
- });
-
- // Put back the initial shape
- nextShapes[this.shape.id] = {
- ...nextShapes[this.shape.id],
- point: this.shape.point,
- };
-
- if (this.initialSiblings) {
- nextShapes[this.shape.parentId] = {
- children: [...this.initialSiblings, this.shape.id],
- };
- }
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes: nextShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: [this.shape.id],
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const { currentPageId } = this.app;
-
- const beforeShapes: Patch> = {};
-
- const afterShapes: Patch> = {};
-
- const afterSelectedIds: string[] = [];
-
- Object.values(this.grid).forEach(id => {
- beforeShapes[id] = undefined;
- afterShapes[id] = this.app.getShape(id);
- afterSelectedIds.push(id);
- // TODO: Add shape to parent if grouped
- });
-
- beforeShapes[this.shape.id] = this.shape;
-
- // Add shapes to parent id
- if (this.initialSiblings) {
- beforeShapes[this.shape.parentId] = {
- children: [...this.initialSiblings, this.shape.id],
- };
-
- afterShapes[this.shape.parentId] = {
- children: [
- ...this.initialSiblings,
- ...Object.values(this.grid),
- ],
- };
- }
-
- // If no new shapes have been created, bail
- if (afterSelectedIds.length === 1) return;
-
- return {
- id: 'grid',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: [],
- hoveredId: undefined,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: afterSelectedIds,
- hoveredId: undefined,
- },
- },
- },
- },
- };
- };
-
- private get_clone = (point: number[], copy: boolean) => {
- const clone = {
- ...this.shape,
- id: Utils.uniqueId(),
- point,
- };
-
- // if (!copy) {
- // if (clone.type === TDShapeType.Sticky) {
- // clone.text = '';
- // }
- // }
-
- return clone;
- };
-}
diff --git a/libs/components/board-sessions/src/handle-session.ts b/libs/components/board-sessions/src/handle-session.ts
deleted file mode 100644
index 614059079..000000000
--- a/libs/components/board-sessions/src/handle-session.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- SessionType,
- ShapesWithProp,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class HandleSession extends BaseSession {
- type = SessionType.Handle;
- performanceMode: undefined;
- status = TDStatus.TranslatingHandle;
- commandId: string;
- topLeft: number[];
- shiftKey = false;
- initialShape: ShapesWithProp<'handles'>;
- handleId: string;
-
- constructor(
- app: TldrawApp,
- shapeId: string,
- handleId: string,
- commandId = 'move_handle'
- ) {
- super(app);
- const { originPoint } = app;
- this.topLeft = [...originPoint];
- this.handleId = handleId;
- this.initialShape = this.app.getShape(shapeId);
- this.commandId = commandId;
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const {
- initialShape,
- app: { currentPageId, currentPoint },
- } = this;
-
- const shape = this.app.getShape>(
- initialShape.id
- );
-
- if (shape.isLocked) return void null;
-
- const handles = shape.handles;
-
- const handleId = this.handleId as keyof typeof handles;
-
- const delta = Vec.sub(currentPoint, handles[handleId].point);
-
- const handleChanges = {
- [handleId]: {
- ...handles[handleId],
- point: Vec.sub(
- Vec.add(handles[handleId].point, delta),
- shape.point
- ),
- },
- };
-
- // First update the handle's next point
- const change = TLDR.get_shape_util(shape).onHandleChange?.(
- shape,
- handleChanges
- );
-
- if (!change) return;
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [shape.id]: change,
- },
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- initialShape,
- app: { currentPageId },
- } = this;
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [initialShape.id]: initialShape,
- },
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- initialShape,
- app: { currentPageId },
- } = this;
-
- return {
- id: this.commandId,
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [initialShape.id]: initialShape,
- },
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [initialShape.id]: TLDR.on_session_complete(
- this.app.getShape(this.initialShape.id)
- ),
- },
- },
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/index.ts b/libs/components/board-sessions/src/index.ts
deleted file mode 100644
index 86a847fa6..000000000
--- a/libs/components/board-sessions/src/index.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { ExceptFirst, SessionType } from '@toeverything/components/board-types';
-import { ArrowSession } from './arrow-session';
-import { BrushSession } from './brush-session';
-import { DrawSession } from './draw-session';
-import { EraseSession } from './erase-session';
-import { GridSession } from './grid-session';
-import { HandleSession } from './handle-session';
-import { LaserSession } from './laser-session';
-import { RotateSession } from './rotate-session';
-import { TransformSession } from './transform-session';
-import { TransformSingleSession } from './transform-single-session';
-import { TranslateSession } from './translate-session';
-
-export type TldrawSession =
- | ArrowSession
- | BrushSession
- | DrawSession
- | HandleSession
- | RotateSession
- | TransformSession
- | TransformSingleSession
- | TranslateSession
- | EraseSession
- | GridSession;
-
-export interface SessionsMap {
- [SessionType.Arrow]: typeof ArrowSession;
- [SessionType.Brush]: typeof BrushSession;
- [SessionType.Draw]: typeof DrawSession;
- [SessionType.Laser]: typeof LaserSession;
- [SessionType.Erase]: typeof EraseSession;
- [SessionType.Handle]: typeof HandleSession;
- [SessionType.Rotate]: typeof RotateSession;
- [SessionType.Transform]: typeof TransformSession;
- [SessionType.TransformSingle]: typeof TransformSingleSession;
- [SessionType.Translate]: typeof TranslateSession;
- [SessionType.Grid]: typeof GridSession;
-}
-
-export type SessionOfType = SessionsMap[K];
-
-export type SessionArgsOfType = ExceptFirst<
- ConstructorParameters>
->;
-
-export const sessions: { [K in SessionType]: SessionsMap[K] } = {
- [SessionType.Arrow]: ArrowSession,
- [SessionType.Brush]: BrushSession,
- [SessionType.Draw]: DrawSession,
- [SessionType.Laser]: LaserSession,
- [SessionType.Erase]: EraseSession,
- [SessionType.Handle]: HandleSession,
- [SessionType.Rotate]: RotateSession,
- [SessionType.Transform]: TransformSession,
- [SessionType.TransformSingle]: TransformSingleSession,
- [SessionType.Translate]: TranslateSession,
- [SessionType.Grid]: GridSession,
-};
-
-export const getSession = (
- type: K
-): SessionOfType => {
- return sessions[type];
-};
diff --git a/libs/components/board-sessions/src/laser-session.ts b/libs/components/board-sessions/src/laser-session.ts
deleted file mode 100644
index ba1c1d5b8..000000000
--- a/libs/components/board-sessions/src/laser-session.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import {
- SessionType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-export class LaserSession extends BaseSession {
- type = SessionType.Draw;
- performanceMode: any = undefined;
- status = TDStatus.Creating;
- prevPoint: number[];
- prevEraseShapesSize = 0;
-
- constructor(app: TldrawApp) {
- super(app);
- this.prevPoint = [...app.originPoint];
- this.interval = this.loop();
- }
-
- interval: any;
- timestamp1 = 0;
- timestamp2 = 0;
- prevErasePoint: number[] = [];
-
- loop = () => {
- const now = Date.now();
- const elapsed1 = now - this.timestamp1;
- const elapsed2 = now - this.timestamp2;
- const { laserLine } = this.app.appState;
-
- let next = [...laserLine];
- let didUpdate = false;
-
- if (elapsed1 > 16 && this.prevErasePoint !== this.prevPoint) {
- didUpdate = true;
- next = [...laserLine, this.prevPoint];
- this.prevErasePoint = this.prevPoint;
- }
-
- if (elapsed2 > 32) {
- if (next.length > 1) {
- didUpdate = true;
- next.splice(0, Math.ceil(next.length * 0.1));
- this.timestamp2 = now;
- }
- }
-
- if (didUpdate) {
- this.app.patchState(
- {
- appState: {
- laserLine: next,
- },
- },
- 'eraseline'
- );
- }
-
- this.interval = requestAnimationFrame(this.loop);
- };
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const { originPoint, currentPoint } = this.app;
-
- const newPoint = Vec.toFixed(
- Vec.add(originPoint, Vec.sub(currentPoint, originPoint))
- );
-
- this.prevPoint = newPoint;
-
- return {};
- };
-
- cancel = (): TldrawPatch | undefined => {
- cancelAnimationFrame(this.interval);
- return {
- appState: {
- laserLine: [],
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const { page } = this.app;
-
- cancelAnimationFrame(this.interval);
-
- return {
- before: {
- appState: {
- laserLine: [],
- },
- },
- after: {
- appState: {
- laserLine: [],
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/rotate-session.ts b/libs/components/board-sessions/src/rotate-session.ts
deleted file mode 100644
index 93c6bad29..000000000
--- a/libs/components/board-sessions/src/rotate-session.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- SessionType,
- TDShape,
- TDShapeType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class RotateSession extends BaseSession {
- type = SessionType.Rotate;
- status = TDStatus.Transforming;
- performanceMode: undefined;
- delta = [0, 0];
- commonBoundsCenter: number[];
- initialAngle: number;
- initialShapes: {
- shape: TDShape;
- center: number[];
- }[];
- changes: Record> = {};
-
- constructor(app: TldrawApp) {
- super(app);
-
- const {
- app: { currentPageId, pageState, originPoint },
- } = this;
-
- const initialShapes = TLDR.get_selected_branch_snapshot(
- app.state,
- currentPageId
- ).filter(shape => !shape.isLocked);
-
- if (initialShapes.length === 0) {
- throw Error('No selected shapes!');
- }
-
- if (app.rotationInfo.selectedIds === pageState.selectedIds) {
- if (app.rotationInfo.center === undefined) {
- throw Error('We should have a center for rotation!');
- }
-
- this.commonBoundsCenter = app.rotationInfo.center;
- } else {
- this.commonBoundsCenter = Utils.getBoundsCenter(
- Utils.getCommonBounds(initialShapes.map(TLDR.get_bounds))
- );
- app.rotationInfo.selectedIds = pageState.selectedIds;
- app.rotationInfo.center = this.commonBoundsCenter;
- }
-
- this.initialShapes = initialShapes
- .filter(shape => shape.children === undefined)
- .map(shape => {
- return {
- shape,
- center: this.app.getShapeUtil(shape).getCenter(shape),
- };
- });
-
- this.initialAngle = Vec.angle(this.commonBoundsCenter, originPoint);
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const {
- commonBoundsCenter,
- initialShapes,
- app: { currentPageId, currentPoint, shiftKey },
- } = this;
-
- const filteredShapes = initialShapes.filter(
- shape => shape.shape.type !== TDShapeType.Editor
- );
-
- const shapes: Record> = {};
-
- let directionDelta =
- Vec.angle(commonBoundsCenter, currentPoint) - this.initialAngle;
-
- if (shiftKey) {
- directionDelta = Utils.snapAngleToSegments(directionDelta, 24); // 15 degrees
- }
-
- // Update the shapes
- filteredShapes.forEach(({ center, shape }) => {
- const { rotation = 0 } = shape;
- let shapeDelta = 0;
-
- if (shiftKey) {
- const snappedRotation = Utils.snapAngleToSegments(rotation, 24);
- shapeDelta = snappedRotation - rotation;
- }
-
- const change = TLDR.get_rotated_shape_mutation(
- shape,
- center,
- commonBoundsCenter,
- shiftKey ? directionDelta + shapeDelta : directionDelta
- );
-
- if (change) {
- shapes[shape.id] = change;
- }
- });
-
- this.changes = shapes;
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes,
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- initialShapes,
- app: { currentPageId },
- } = this;
-
- const shapes: Record = {};
- initialShapes.forEach(({ shape }) => (shapes[shape.id] = shape));
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes,
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- initialShapes,
- app: { currentPageId },
- } = this;
-
- const beforeShapes = {} as Record>;
- const afterShapes = this.changes;
-
- initialShapes.forEach(({ shape: { id, point, rotation, handles } }) => {
- beforeShapes[id] = { point, rotation, handles };
- });
-
- return {
- id: 'rotate',
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- },
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/transform-session.ts b/libs/components/board-sessions/src/transform-session.ts
deleted file mode 100644
index 966e9e8bd..000000000
--- a/libs/components/board-sessions/src/transform-session.ts
+++ /dev/null
@@ -1,384 +0,0 @@
-import type { TLBoundsWithCenter, TLSnapLine } from '@tldraw/core';
-import { TLBounds, TLBoundsCorner, TLBoundsEdge, Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- SessionType,
- SLOW_SPEED,
- SNAP_DISTANCE,
- TDShape,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-type SnapInfo =
- | {
- state: 'empty';
- }
- | {
- state: 'ready';
- bounds: TLBoundsWithCenter[];
- };
-
-export class TransformSession extends BaseSession {
- type = SessionType.Transform;
- performanceMode: undefined;
- status = TDStatus.Transforming;
- scaleX = 1;
- scaleY = 1;
- initialShapes: TDShape[];
- initialShapeIds: string[];
- initialSelectedIds: string[];
- shapeBounds: {
- initialShape: TDShape;
- initialShapeBounds: TLBounds;
- transformOrigin: number[];
- }[];
- hasUnlockedShapes: boolean;
- isAllAspectRatioLocked: boolean;
- initialCommonBounds: TLBounds;
- snapInfo: SnapInfo = { state: 'empty' };
- prevPoint = [0, 0];
- speed = 1;
-
- constructor(
- app: TldrawApp,
- public transformType:
- | TLBoundsEdge
- | TLBoundsCorner = TLBoundsCorner.BottomRight,
- public isCreate = false
- ) {
- super(app);
- this.initialSelectedIds = [...this.app.selectedIds];
- this.app.rotationInfo.selectedIds = [...this.initialSelectedIds];
-
- this.initialShapes = TLDR.get_selected_branch_snapshot(
- this.app.state,
- this.app.currentPageId
- ).filter(shape => !shape.isLocked);
-
- this.initialShapeIds = this.initialShapes.map(shape => shape.id);
-
- this.hasUnlockedShapes = this.initialShapes.length > 0;
-
- this.isAllAspectRatioLocked = this.initialShapes.every(
- shape =>
- shape.isAspectRatioLocked ||
- TLDR.get_shape_util(shape).isAspectRatioLocked
- );
-
- const shapesBounds = Object.fromEntries(
- this.initialShapes.map(shape => [shape.id, TLDR.get_bounds(shape)])
- );
-
- const boundsArr = Object.values(shapesBounds);
-
- this.initialCommonBounds = Utils.getCommonBounds(boundsArr);
-
- const initialInnerBounds = Utils.getBoundsFromPoints(
- boundsArr.map(Utils.getBoundsCenter)
- );
-
- // Return a mapping of shapes to bounds together with the relative
- // positions of the shape's bounds within the common bounds shape.
- this.shapeBounds = this.initialShapes.map(shape => {
- const initialShapeBounds = shapesBounds[shape.id];
- const ic = Utils.getBoundsCenter(initialShapeBounds);
-
- const ix =
- (ic[0] - initialInnerBounds.minX) / initialInnerBounds.width;
- const iy =
- (ic[1] - initialInnerBounds.minY) / initialInnerBounds.height;
-
- return {
- initialShape: shape,
- initialShapeBounds,
- transformOrigin: [ix, iy],
- };
- });
- }
-
- start = (): TldrawPatch | undefined => {
- this.snapInfo = {
- state: 'ready',
- bounds: this.app.shapes
- .filter(shape => !this.initialShapeIds.includes(shape.id))
- .map(shape =>
- Utils.getBoundsWithCenter(TLDR.get_rotated_bounds(shape))
- ),
- };
-
- return void null;
- };
-
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- update = (): TldrawPatch | undefined => {
- const {
- transformType,
- shapeBounds,
- initialCommonBounds,
- isAllAspectRatioLocked,
- app: {
- currentPageId,
- pageState: { camera },
- viewport,
- currentPoint,
- previousPoint,
- originPoint,
- shiftKey,
- altKey,
- metaKey,
- currentGrid,
- settings: { isSnapping, showGrid },
- },
- } = this;
-
- const shapes = {} as Record;
-
- const delta = altKey
- ? Vec.mul(Vec.sub(currentPoint, originPoint), 2)
- : Vec.sub(currentPoint, originPoint);
-
- let newBounds = Utils.getTransformedBoundingBox(
- initialCommonBounds,
- transformType,
- delta,
- 0,
- shiftKey || isAllAspectRatioLocked
- );
-
- if (altKey) {
- newBounds = {
- ...newBounds,
- ...Utils.centerBounds(
- newBounds,
- Utils.getBoundsCenter(initialCommonBounds)
- ),
- };
- }
-
- if (showGrid) {
- newBounds = {
- ...newBounds,
- ...Utils.snapBoundsToGrid(newBounds, currentGrid),
- };
- }
-
- // Should we snap?
-
- const speed = Vec.dist(currentPoint, previousPoint);
-
- const speedChange = speed - this.speed;
-
- this.speed = this.speed + speedChange * (speedChange > 1 ? 0.5 : 0.15);
-
- let snapLines: TLSnapLine[] = [];
-
- if (
- ((isSnapping && !metaKey) || (!isSnapping && metaKey)) &&
- this.speed * camera.zoom < SLOW_SPEED &&
- this.snapInfo.state === 'ready'
- ) {
- const snapResult = Utils.getSnapPoints(
- Utils.getBoundsWithCenter(newBounds),
- this.snapInfo.bounds.filter(
- bounds =>
- Utils.boundsContain(viewport, bounds) ||
- Utils.boundsCollide(viewport, bounds)
- ),
- SNAP_DISTANCE / camera.zoom
- );
-
- if (snapResult) {
- snapLines = snapResult.snapLines;
-
- newBounds = Utils.getTransformedBoundingBox(
- initialCommonBounds,
- transformType,
- Vec.sub(delta, snapResult.offset),
- 0,
- shiftKey || isAllAspectRatioLocked
- );
- }
- }
-
- // Now work backward to calculate a new bounding box for each of the shapes.
-
- this.scaleX = newBounds.scaleX;
- this.scaleY = newBounds.scaleY;
-
- shapeBounds.forEach(
- ({ initialShape, initialShapeBounds, transformOrigin }) => {
- let newShapeBounds = Utils.getRelativeTransformedBoundingBox(
- newBounds,
- initialCommonBounds,
- initialShapeBounds,
- this.scaleX < 0,
- this.scaleY < 0
- );
-
- if (showGrid) {
- newShapeBounds = Utils.snapBoundsToGrid(
- newShapeBounds,
- currentGrid
- );
- }
-
- const afterShape = TLDR.transform(
- this.app.getShape(initialShape.id),
- newShapeBounds,
- {
- type: this.transformType,
- initialShape,
- scaleX: this.scaleX,
- scaleY: this.scaleY,
- transformOrigin,
- }
- );
-
- shapes[initialShape.id] = afterShape;
- }
- );
-
- return {
- appState: {
- snapLines,
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes,
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- shapeBounds,
- app: { currentPageId },
- } = this;
-
- const shapes = {} as Record;
-
- if (this.isCreate) {
- shapeBounds.forEach(
- shape => (shapes[shape.initialShape.id] = undefined)
- );
- } else {
- shapeBounds.forEach(
- shape => (shapes[shape.initialShape.id] = shape.initialShape)
- );
- }
-
- return {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: this.isCreate
- ? []
- : shapeBounds.map(shape => shape.initialShape.id),
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- isCreate,
- shapeBounds,
- hasUnlockedShapes,
- app: { currentPageId },
- } = this;
-
- if (!hasUnlockedShapes) return;
-
- if (
- this.isCreate &&
- Vec.dist(this.app.originPoint, this.app.currentPoint) < 2
- ) {
- return this.cancel();
- }
-
- const beforeShapes: Record = {};
- const afterShapes: Record = {};
-
- let beforeSelectedIds: string[];
- let afterSelectedIds: string[];
-
- if (isCreate) {
- beforeSelectedIds = [];
- afterSelectedIds = [];
- shapeBounds.forEach(({ initialShape }) => {
- beforeShapes[initialShape.id] = undefined;
- afterShapes[initialShape.id] = this.app.getShape(
- initialShape.id
- );
- });
- } else {
- beforeSelectedIds = this.initialSelectedIds;
- afterSelectedIds = this.initialSelectedIds;
- shapeBounds.forEach(({ initialShape }) => {
- beforeShapes[initialShape.id] = initialShape;
- afterShapes[initialShape.id] = this.app.getShape(
- initialShape.id
- );
- });
- }
-
- return {
- id: 'transform',
- before: {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: beforeSelectedIds,
- hoveredId: undefined,
- editingId: undefined,
- },
- },
- },
- },
- after: {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: afterSelectedIds,
- hoveredId: undefined,
- editingId: undefined,
- },
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/transform-single-session.ts b/libs/components/board-sessions/src/transform-single-session.ts
deleted file mode 100644
index 483c0d0cd..000000000
--- a/libs/components/board-sessions/src/transform-single-session.ts
+++ /dev/null
@@ -1,310 +0,0 @@
-import {
- TLBounds,
- TLBoundsCorner,
- TLBoundsEdge,
- TLBoundsWithCenter,
- TLSnapLine,
- Utils,
-} from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- SessionType,
- SLOW_SPEED,
- SNAP_DISTANCE,
- TDShape,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-type SnapInfo =
- | {
- state: 'empty';
- }
- | {
- state: 'ready';
- bounds: TLBoundsWithCenter[];
- };
-
-export class TransformSingleSession extends BaseSession {
- type = SessionType.TransformSingle;
- status = TDStatus.Transforming;
- performanceMode: undefined;
- transformType: TLBoundsEdge | TLBoundsCorner;
- scaleX = 1;
- scaleY = 1;
- isCreate: boolean;
- initialShape: TDShape;
- initialShapeBounds: TLBounds;
- initialCommonBounds: TLBounds;
- snapInfo: SnapInfo = { state: 'empty' };
- prevPoint = [0, 0];
- speed = 1;
-
- constructor(
- app: TldrawApp,
- id: string,
- transformType: TLBoundsEdge | TLBoundsCorner,
- isCreate = false
- ) {
- super(app);
- this.isCreate = isCreate;
- this.transformType = transformType;
-
- const shape = this.app.getShape(id);
- this.initialShape = shape;
- this.initialShapeBounds = TLDR.get_bounds(shape);
- this.initialCommonBounds = TLDR.get_rotated_bounds(shape);
- this.app.rotationInfo.selectedIds = [shape.id];
- }
-
- start = (): TldrawPatch | undefined => {
- this.snapInfo = {
- state: 'ready',
- bounds: this.app.shapes
- .filter(shape => shape.id !== this.initialShape.id)
- .map(shape =>
- Utils.getBoundsWithCenter(TLDR.get_rotated_bounds(shape))
- ),
- };
-
- return void null;
- };
-
- update = (): TldrawPatch | undefined => {
- const {
- transformType,
- initialShape,
- initialShapeBounds,
- app: {
- settings: { isSnapping, showGrid },
- currentPageId,
- pageState: { camera },
- viewport,
- currentPoint,
- previousPoint,
- originPoint,
- currentGrid,
- shiftKey,
- altKey,
- metaKey,
- },
- } = this;
-
- if (initialShape.isLocked) return void null;
-
- const shapes = {} as Record>;
-
- const delta = altKey
- ? Vec.mul(Vec.sub(currentPoint, originPoint), 2)
- : Vec.sub(currentPoint, originPoint);
-
- const shape = this.app.getShape(initialShape.id);
-
- const utils = TLDR.get_shape_util(shape);
-
- let newBounds = Utils.getTransformedBoundingBox(
- initialShapeBounds,
- transformType,
- delta,
- shape.rotation,
- shiftKey || shape.isAspectRatioLocked || utils.isAspectRatioLocked
- );
-
- if (altKey) {
- newBounds = {
- ...newBounds,
- ...Utils.centerBounds(
- newBounds,
- Utils.getBoundsCenter(initialShapeBounds)
- ),
- };
- }
-
- if (showGrid) {
- newBounds = {
- ...newBounds,
- ...Utils.snapBoundsToGrid(newBounds, currentGrid),
- };
- }
-
- // Should we snap?
-
- const speed = Vec.dist(currentPoint, previousPoint);
-
- const speedChange = speed - this.speed;
-
- this.speed = this.speed + speedChange * (speedChange > 1 ? 0.5 : 0.15);
-
- let snapLines: TLSnapLine[] = [];
-
- if (
- ((isSnapping && !metaKey) || (!isSnapping && metaKey)) &&
- !initialShape.rotation && // not now anyway
- this.speed * camera.zoom < SLOW_SPEED &&
- this.snapInfo.state === 'ready'
- ) {
- const snapResult = Utils.getSnapPoints(
- Utils.getBoundsWithCenter(newBounds),
- this.snapInfo.bounds.filter(
- bounds =>
- Utils.boundsContain(viewport, bounds) ||
- Utils.boundsCollide(viewport, bounds)
- ),
- SNAP_DISTANCE / camera.zoom
- );
-
- if (snapResult) {
- snapLines = snapResult.snapLines;
-
- newBounds = Utils.getTransformedBoundingBox(
- initialShapeBounds,
- transformType,
- Vec.sub(delta, snapResult.offset),
- shape.rotation,
- shiftKey ||
- shape.isAspectRatioLocked ||
- utils.isAspectRatioLocked
- );
- }
- }
-
- const afterShape = TLDR.get_shape_util(shape).transformSingle(
- shape,
- newBounds,
- {
- initialShape,
- type: this.transformType,
- scaleX: newBounds.scaleX,
- scaleY: newBounds.scaleY,
- transformOrigin: [0.5, 0.5],
- }
- );
-
- if (afterShape) {
- shapes[shape.id] = afterShape;
- }
-
- if (showGrid && afterShape && afterShape.point) {
- afterShape.point = Vec.snap(afterShape.point, currentGrid);
- }
-
- return {
- appState: {
- snapLines,
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes,
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- initialShape,
- app: { currentPageId },
- } = this;
-
- const shapes = {} as Record;
-
- if (this.isCreate) {
- shapes[initialShape.id] = undefined;
- } else {
- shapes[initialShape.id] = initialShape;
- }
-
- return {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: this.isCreate ? [] : [initialShape.id],
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- initialShape,
- app: { currentPageId },
- } = this;
-
- if (initialShape.isLocked) return;
-
- if (
- this.isCreate &&
- Vec.dist(this.app.originPoint, this.app.currentPoint) < 2
- ) {
- return this.cancel();
- }
-
- const beforeShapes = {} as Record | undefined>;
- const afterShapes = {} as Record>;
-
- beforeShapes[initialShape.id] = this.isCreate
- ? undefined
- : initialShape;
-
- afterShapes[initialShape.id] = TLDR.on_session_complete(
- this.app.getShape(initialShape.id)
- );
-
- return {
- id: 'transform_single',
- before: {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: this.isCreate ? [] : [initialShape.id],
- editingId: undefined,
- hoveredId: undefined,
- },
- },
- },
- },
- after: {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: [initialShape.id],
- editingId: undefined,
- hoveredId: undefined,
- },
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/translate-label-session.ts b/libs/components/board-sessions/src/translate-label-session.ts
deleted file mode 100644
index f490fd0b8..000000000
--- a/libs/components/board-sessions/src/translate-label-session.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import type { TLBounds } from '@tldraw/core';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- ArrowShape,
- EllipseShape,
- RectangleShape,
- SessionType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
- TriangleShape,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-export class TranslateLabelSession extends BaseSession {
- type = SessionType.Handle;
- performanceMode: undefined;
- status = TDStatus.TranslatingHandle;
- initialShape: RectangleShape | TriangleShape | EllipseShape | ArrowShape;
- initialShapeBounds: TLBounds;
-
- constructor(app: TldrawApp, shapeId: string) {
- super(app);
- this.initialShape = this.app.getShape<
- RectangleShape | TriangleShape | EllipseShape | ArrowShape
- >(shapeId);
- this.initialShapeBounds = this.app.getShapeBounds(shapeId);
- }
-
- start = (): TldrawPatch | undefined => void null;
-
- update = (): TldrawPatch | undefined => {
- const {
- initialShapeBounds,
- app: { currentPageId, currentPoint },
- } = this;
-
- const newHandlePoint = [
- Math.max(
- 0,
- Math.min(1, currentPoint[0] / initialShapeBounds.width)
- ),
- Math.max(
- 0,
- Math.min(1, currentPoint[1] / initialShapeBounds.height)
- ),
- ];
-
- // First update the handle's next point
- const change = {
- handlePoint: newHandlePoint,
- } as Partial<
- RectangleShape | TriangleShape | EllipseShape | ArrowShape
- >;
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [this.initialShape.id]: change,
- },
- },
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- initialShape,
- app: { currentPageId },
- } = this;
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [initialShape.id]: initialShape,
- },
- },
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- initialShape,
- app: { currentPageId },
- } = this;
-
- return {
- before: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [initialShape.id]: initialShape,
- },
- },
- },
- },
- },
- after: {
- document: {
- pages: {
- [currentPageId]: {
- shapes: {
- [initialShape.id]: TLDR.on_session_complete(
- this.app.getShape(this.initialShape.id)
- ),
- },
- },
- },
- },
- },
- };
- };
-}
diff --git a/libs/components/board-sessions/src/translate-session.ts b/libs/components/board-sessions/src/translate-session.ts
deleted file mode 100644
index 59f227d86..000000000
--- a/libs/components/board-sessions/src/translate-session.ts
+++ /dev/null
@@ -1,823 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import {
- TLBounds,
- TLBoundsWithCenter,
- TLPageState,
- TLSnapLine,
- Utils,
-} from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import type { TldrawApp } from '@toeverything/components/board-state';
-import { TLDR } from '@toeverything/components/board-state';
-import {
- ArrowBinding,
- ArrowShape,
- GroupShape,
- Patch,
- SessionType,
- SLOW_SPEED,
- SNAP_DISTANCE,
- TDBinding,
- TDShape,
- TDShapeType,
- TDStatus,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { BaseSession } from './base-session';
-
-type CloneInfo =
- | {
- state: 'empty';
- }
- | {
- state: 'ready';
- cloneMap: Record;
- clones: TDShape[];
- clonedBindings: ArrowBinding[];
- };
-
-type SnapInfo =
- | {
- state: 'empty';
- }
- | {
- state: 'ready';
- others: TLBoundsWithCenter[];
- bounds: TLBoundsWithCenter[];
- };
-
-export class TranslateSession extends BaseSession {
- performanceMode: undefined;
- type = SessionType.Translate;
- status = TDStatus.Translating;
- delta = [0, 0];
- prev = [0, 0];
- prevPoint = [0, 0];
- speed = 1;
- cloneInfo: CloneInfo = {
- state: 'empty',
- };
- snapInfo: SnapInfo = {
- state: 'empty',
- };
- snapLines: TLSnapLine[] = [];
- isCloning = false;
- isCreate: boolean;
- link: 'left' | 'right' | 'center' | false;
-
- initialIds: Set;
- hasUnlockedShapes: boolean;
- initialSelectedIds: string[];
- initialCommonBounds: TLBounds;
- initialShapes: TDShape[];
- initialParentChildren: Record;
- bindingsToDelete: ArrowBinding[];
-
- constructor(
- app: TldrawApp,
- isCreate = false,
- link: 'left' | 'right' | 'center' | false = false
- ) {
- super(app);
- this.isCreate = isCreate;
- this.link = link;
-
- const { currentPageId, selectedIds, page } = this.app;
- this.initialSelectedIds = [...selectedIds];
-
- const selectedShapes = (
- link
- ? TLDR.get_linked_shape_ids(
- this.app.state,
- currentPageId,
- link,
- false
- )
- : selectedIds
- )
- .map(id => this.app.getShape(id))
- .filter(shape => !shape.isLocked);
-
- const selectedShapeIds = new Set(selectedShapes.map(shape => shape.id));
-
- selectedIds.forEach(item => {
- // let shap = this.app.page.shapes[selectedIds[]];
- const shap = this.app.getShape(item);
- if (shap.type === TDShapeType.Frame) {
- Object.entries(this.app.page.shapes).map(([id, shapItem]) => {
- if (id !== shap.id) {
- if (
- Utils.boundsContain(
- TLDR.get_bounds(shap),
- TLDR.get_bounds(shapItem)
- ) &&
- !shapItem.isLocked
- ) {
- selectedShapes.push(shapItem);
- }
- }
- });
- }
- });
-
- // }
- this.hasUnlockedShapes = selectedShapes.length > 0;
- this.initialShapes = Array.from(
- new Set(
- selectedShapes
- .filter(shape => !selectedShapeIds.has(shape.parentId))
- .flatMap(shape => {
- return shape.children
- ? [
- shape,
- ...shape.children.map(childId =>
- this.app.getShape(childId)
- ),
- ]
- : [shape];
- })
- ).values()
- );
-
- this.initialIds = new Set(this.initialShapes.map(shape => shape.id));
-
- this.bindingsToDelete = [];
-
- Object.values(page.bindings)
- .filter(
- binding =>
- this.initialIds.has(binding.fromId) ||
- this.initialIds.has(binding.toId)
- )
- .forEach(binding => {
- if (this.initialIds.has(binding.fromId)) {
- if (!this.initialIds.has(binding.toId)) {
- this.bindingsToDelete.push(binding);
- }
- }
- });
-
- this.initialParentChildren = {};
-
- this.initialShapes
- .map(s => s.parentId)
- .filter(id => id !== page.id)
- .forEach(id => {
- this.initialParentChildren[id] =
- this.app.getShape(id).children!;
- });
-
- this.initialCommonBounds = Utils.getCommonBounds(
- this.initialShapes.map(TLDR.get_rotated_bounds)
- );
-
- this.app.rotationInfo.selectedIds = [...this.app.selectedIds];
- }
-
- start = (): TldrawPatch | undefined => {
- const {
- bindingsToDelete,
- initialIds,
- app: { currentPageId, page },
- } = this;
-
- const allBounds: TLBoundsWithCenter[] = [];
- const otherBounds: TLBoundsWithCenter[] = [];
-
- Object.values(page.shapes).forEach(shape => {
- const bounds = Utils.getBoundsWithCenter(
- TLDR.get_rotated_bounds(shape)
- );
- allBounds.push(bounds);
- if (!initialIds.has(shape.id)) {
- otherBounds.push(bounds);
- }
- });
-
- this.snapInfo = {
- state: 'ready',
- bounds: allBounds,
- others: otherBounds,
- };
-
- if (bindingsToDelete.length === 0) return;
-
- const nextBindings: Patch> = {};
-
- bindingsToDelete.forEach(
- binding => (nextBindings[binding.id] = undefined)
- );
-
- return {
- document: {
- pages: {
- [currentPageId]: {
- bindings: nextBindings,
- },
- },
- },
- };
- };
-
- update = (): TldrawPatch | undefined => {
- const {
- initialParentChildren,
- initialShapes,
- initialCommonBounds,
- bindingsToDelete,
- app: {
- pageState: { camera },
- settings: { isSnapping, showGrid },
- currentPageId,
- viewport,
- selectedIds,
- currentPoint,
- previousPoint,
- originPoint,
- altKey,
- shiftKey,
- metaKey,
- currentGrid,
- },
- } = this;
-
- const nextBindings: Patch> = {};
- const nextShapes: Patch> = {};
- const nextPageState: Patch = {};
-
- let delta = Vec.sub(currentPoint, originPoint);
-
- let didChangeCloning = false;
-
- if (!this.isCreate) {
- if (altKey && !this.isCloning) {
- this.isCloning = true;
- didChangeCloning = true;
- } else if (!altKey && this.isCloning) {
- this.isCloning = false;
- didChangeCloning = true;
- }
- }
-
- if (shiftKey) {
- if (Math.abs(delta[0]) < Math.abs(delta[1])) {
- delta[0] = 0;
- } else {
- delta[1] = 0;
- }
- }
-
- // Should we snap?
-
- // Speed is used to decide which snap points to use. At a high
- // speed, we don't use any snap points. At a low speed, we only
- // allow center-to-center snap points. At very low speed, we
- // enable all snap points (still preferring middle snaps). We're
- // using an acceleration function here to smooth the changes in
- // speed, but we also want the speed to accelerate faster than
- // it decelerates.
-
- const speed = Vec.dist(currentPoint, previousPoint);
-
- const change = speed - this.speed;
-
- this.speed = this.speed + change * (change > 1 ? 0.5 : 0.15);
-
- this.snapLines = [];
-
- if (
- ((isSnapping && !metaKey) || (!isSnapping && metaKey)) &&
- this.speed * camera.zoom < SLOW_SPEED &&
- this.snapInfo.state === 'ready'
- ) {
- const snapResult = Utils.getSnapPoints(
- Utils.getBoundsWithCenter(
- showGrid
- ? Utils.snapBoundsToGrid(
- Utils.translateBounds(initialCommonBounds, delta),
- currentGrid
- )
- : Utils.translateBounds(initialCommonBounds, delta)
- ),
- (this.isCloning
- ? this.snapInfo.bounds
- : this.snapInfo.others
- ).filter(bounds => {
- return (
- Utils.boundsContain(viewport, bounds) ||
- Utils.boundsCollide(viewport, bounds)
- );
- }),
- SNAP_DISTANCE / camera.zoom
- );
-
- if (snapResult) {
- this.snapLines = snapResult.snapLines;
- delta = Vec.sub(delta, snapResult.offset);
- }
- }
-
- // We've now calculated the "delta", or difference between the
- // cursor's position (real or adjusted by snaps or axis locking)
- // and the cursor's original position ("origin").
-
- // The "movement" is the actual change of position between this
- // computed position and the previous computed position.
-
- this.prev = delta;
-
- // If cloning...
- if (this.isCloning) {
- // Not Cloning -> Cloning
- if (didChangeCloning) {
- if (this.cloneInfo.state === 'empty') {
- this.create_clone_info();
- }
-
- if (this.cloneInfo.state === 'empty') {
- throw Error;
- }
-
- const { clones, clonedBindings } = this.cloneInfo;
-
- this.isCloning = true;
-
- // Put back any bindings we deleted
- bindingsToDelete.forEach(
- binding => (nextBindings[binding.id] = binding)
- );
-
- // Move original shapes back to start
- initialShapes.forEach(
- shape => (nextShapes[shape.id] = { point: shape.point })
- );
-
- // Add the clones to the page
- clones.forEach(clone => {
- nextShapes[clone.id] = { ...clone };
-
- // Add clones to non-selected parents
- if (
- clone.parentId !== currentPageId &&
- !selectedIds.includes(clone.parentId)
- ) {
- const children =
- nextShapes[clone.parentId]?.children ||
- initialParentChildren[clone.parentId];
-
- if (!children.includes(clone.id)) {
- nextShapes[clone.parentId] = {
- ...nextShapes[clone.parentId],
- children: [...children, clone.id],
- };
- }
- }
- });
-
- // Add the cloned bindings
- for (const binding of clonedBindings) {
- nextBindings[binding.id] = binding;
- }
-
- // Set the selected ids to the clones
- nextPageState.selectedIds = clones.map(clone => clone.id);
-
- // Either way, move the clones
- clones.forEach(clone => {
- nextShapes[clone.id] = {
- ...clone,
- point: showGrid
- ? Vec.snap(
- Vec.toFixed(Vec.add(clone.point, delta)),
- currentGrid
- )
- : Vec.toFixed(Vec.add(clone.point, delta)),
- };
- });
- } else {
- if (this.cloneInfo.state === 'empty') throw Error;
-
- const { clones } = this.cloneInfo;
-
- clones.forEach(clone => {
- nextShapes[clone.id] = {
- point: showGrid
- ? Vec.snap(
- Vec.toFixed(Vec.add(clone.point, delta)),
- currentGrid
- )
- : Vec.toFixed(Vec.add(clone.point, delta)),
- };
- });
- }
- } else {
- // If not cloning...
-
- // Cloning -> Not Cloning
- if (didChangeCloning) {
- if (this.cloneInfo.state === 'empty') throw Error;
-
- const { clones, clonedBindings } = this.cloneInfo;
-
- this.isCloning = false;
-
- // Delete the bindings
- bindingsToDelete.forEach(
- binding => (nextBindings[binding.id] = undefined)
- );
-
- // Remove the clones from parents
- clones.forEach(clone => {
- if (clone.parentId !== currentPageId) {
- nextShapes[clone.parentId] = {
- ...nextShapes[clone.parentId],
- children: initialParentChildren[clone.parentId],
- };
- }
- });
-
- // Delete the clones (including any parent clones)
- clones.forEach(clone => (nextShapes[clone.id] = undefined));
-
- // Move the original shapes back to the cursor position
- initialShapes.forEach(shape => {
- nextShapes[shape.id] = {
- point: showGrid
- ? Vec.snap(
- Vec.toFixed(Vec.add(shape.point, delta)),
- currentGrid
- )
- : Vec.toFixed(Vec.add(shape.point, delta)),
- };
- });
-
- // Delete the cloned bindings
- for (const binding of clonedBindings) {
- nextBindings[binding.id] = undefined;
- }
-
- // Set selected ids
- nextPageState.selectedIds = initialShapes.map(
- shape => shape.id
- );
- } else {
- // Move the shapes by the delta
- initialShapes.forEach(shape => {
- // const current = (nextShapes[shape.id] || this.app.getShape(shape.id)) as TDShape
-
- nextShapes[shape.id] = {
- point: showGrid
- ? Vec.snap(
- Vec.toFixed(Vec.add(shape.point, delta)),
- currentGrid
- )
- : Vec.toFixed(Vec.add(shape.point, delta)),
- };
- });
- }
- }
-
- return {
- appState: {
- snapLines: this.snapLines,
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: nextShapes,
- bindings: nextBindings,
- },
- },
- pageStates: {
- [currentPageId]: nextPageState,
- },
- },
- };
- };
-
- cancel = (): TldrawPatch | undefined => {
- const {
- initialShapes,
- initialSelectedIds,
- bindingsToDelete,
- app: { currentPageId },
- } = this;
-
- const nextBindings: Record | undefined> = {};
- const nextShapes: Record | undefined> = {};
- const nextPageState: Partial = {
- editingId: undefined,
- hoveredId: undefined,
- };
-
- // Put back any deleted bindings
- bindingsToDelete.forEach(
- binding => (nextBindings[binding.id] = binding)
- );
-
- if (this.isCreate) {
- initialShapes.forEach(({ id }) => (nextShapes[id] = undefined));
- nextPageState.selectedIds = [];
- } else {
- // Put initial shapes back to where they started
- initialShapes.forEach(
- ({ id, point }) =>
- (nextShapes[id] = { ...nextShapes[id], point })
- );
- nextPageState.selectedIds = initialSelectedIds;
- }
-
- if (this.cloneInfo.state === 'ready') {
- const { clones, clonedBindings } = this.cloneInfo;
- // Delete clones
- clones.forEach(clone => (nextShapes[clone.id] = undefined));
-
- // Delete cloned bindings
- clonedBindings.forEach(
- binding => (nextBindings[binding.id] = undefined)
- );
- }
-
- return {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: nextShapes,
- bindings: nextBindings,
- },
- },
- pageStates: {
- [currentPageId]: nextPageState,
- },
- },
- };
- };
-
- complete = (): TldrawPatch | TldrawCommand | undefined => {
- const {
- initialShapes,
- initialParentChildren,
- bindingsToDelete,
- app: { currentPageId },
- } = this;
-
- const beforeBindings: Patch> = {};
- const beforeShapes: Patch> = {};
-
- const afterBindings: Patch> = {};
- const afterShapes: Patch> = {};
-
- if (this.isCloning) {
- if (this.cloneInfo.state === 'empty') {
- this.create_clone_info();
- }
-
- if (this.cloneInfo.state !== 'ready') throw Error;
- const { clones, clonedBindings } = this.cloneInfo;
-
- // Update the clones
- clones.forEach(clone => {
- beforeShapes[clone.id] = undefined;
-
- afterShapes[clone.id] = this.app.getShape(clone.id);
-
- if (clone.parentId !== currentPageId) {
- beforeShapes[clone.parentId] = {
- ...beforeShapes[clone.parentId],
- children: initialParentChildren[clone.parentId],
- };
-
- afterShapes[clone.parentId] = {
- ...afterShapes[clone.parentId],
- children: this.app.getShape(clone.parentId)
- .children,
- };
- }
- });
-
- // Update the cloned bindings
- clonedBindings.forEach(binding => {
- beforeBindings[binding.id] = undefined;
- afterBindings[binding.id] = this.app.getBinding(binding.id);
- });
- } else {
- // If we aren't cloning, then update the initial shapes
- initialShapes.forEach(shape => {
- beforeShapes[shape.id] = this.isCreate
- ? undefined
- : {
- ...beforeShapes[shape.id],
- point: shape.point,
- };
-
- afterShapes[shape.id] = {
- ...afterShapes[shape.id],
- ...(this.isCreate
- ? this.app.getShape(shape.id)
- : { point: this.app.getShape(shape.id).point }),
- };
- });
- }
-
- // Update the deleted bindings and any associated shapes
- bindingsToDelete.forEach(binding => {
- beforeBindings[binding.id] = binding;
-
- for (const id of [binding.toId, binding.fromId]) {
- // Let's also look at the bound shape...
- const shape = this.app.getShape(id);
-
- // If the bound shape has a handle that references the deleted binding, delete that reference
- if (!shape.handles) continue;
-
- Object.values(shape.handles)
- .filter(handle => handle.bindingId === binding.id)
- .forEach(handle => {
- beforeShapes[id] = { ...beforeShapes[id], handles: {} };
-
- afterShapes[id] = { ...afterShapes[id], handles: {} };
-
- // There should be before and after shapes
-
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- beforeShapes[id]!.handles![
- handle.id as keyof ArrowShape['handles']
- ] = {
- bindingId: binding.id,
- };
-
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- afterShapes[id]!.handles![
- handle.id as keyof ArrowShape['handles']
- ] = {
- bindingId: undefined,
- };
- });
- }
- });
-
- return {
- id: 'translate',
- before: {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: beforeShapes,
- bindings: beforeBindings,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: this.isCreate
- ? []
- : [...this.initialSelectedIds],
- },
- },
- },
- },
- after: {
- appState: {
- snapLines: [],
- },
- document: {
- pages: {
- [currentPageId]: {
- shapes: afterShapes,
- bindings: afterBindings,
- },
- },
- pageStates: {
- [currentPageId]: {
- selectedIds: [...this.app.selectedIds],
- },
- },
- },
- },
- };
- };
-
- private create_clone_info = () => {
- // Create clones when as they're needed.
- // Consider doing this work in a worker.
-
- const {
- initialShapes,
- initialParentChildren,
- app: { selectedIds, currentPageId, page },
- } = this;
-
- const cloneMap: Record = {};
- const clonedBindingsMap: Record = {};
- const clonedBindings: TDBinding[] = [];
-
- // Create clones of selected shapes
- const clones: TDShape[] = [];
-
- initialShapes.forEach(shape => {
- const newId = Utils.uniqueId();
-
- initialParentChildren[newId] = initialParentChildren[shape.id];
-
- cloneMap[shape.id] = newId;
-
- const clone = {
- ...Utils.deepClone(shape),
- id: newId,
- parentId: shape.parentId,
- childIndex: TLDR.get_child_index_above(
- this.app.state,
- shape.id,
- currentPageId
- ),
- };
-
- if (clone.type === TDShapeType.Video) {
- const element = document.getElementById(
- shape.id + '_video'
- ) as HTMLVideoElement;
- if (element)
- clone.currentTime =
- (element.currentTime + 16) % element.duration;
- }
-
- clones.push(clone);
- });
-
- clones.forEach(clone => {
- if (clone.children !== undefined) {
- clone.children = clone.children.map(
- childId => cloneMap[childId]
- );
- }
- });
-
- clones.forEach(clone => {
- if (selectedIds.includes(clone.parentId)) {
- clone.parentId = cloneMap[clone.parentId];
- }
- });
-
- // Potentially confusing name here: these are the ids of the
- // original shapes that were cloned, not their clones' ids.
- const clonedShapeIds = new Set(Object.keys(cloneMap));
-
- // Create cloned bindings for shapes where both to and from shapes are selected
- // (if the user clones, then we will create a new binding for the clones)
- Object.values(page.bindings)
- .filter(
- binding =>
- clonedShapeIds.has(binding.fromId) ||
- clonedShapeIds.has(binding.toId)
- )
- .forEach(binding => {
- if (clonedShapeIds.has(binding.fromId)) {
- if (clonedShapeIds.has(binding.toId)) {
- const cloneId = Utils.uniqueId();
- const cloneBinding = {
- ...Utils.deepClone(binding),
- id: cloneId,
- fromId: cloneMap[binding.fromId] || binding.fromId,
- toId: cloneMap[binding.toId] || binding.toId,
- };
- clonedBindingsMap[binding.id] = cloneId;
- clonedBindings.push(cloneBinding);
- }
- }
- });
-
- // Assign new binding ids to clones (or delete them!)
- clones.forEach(clone => {
- if (clone.handles) {
- if (clone.handles) {
- for (const id in clone.handles) {
- const handle =
- clone.handles[id as keyof ArrowShape['handles']];
- handle.bindingId = handle.bindingId
- ? clonedBindingsMap[handle.bindingId]
- : undefined;
- }
- }
- }
- });
-
- clones.forEach(clone => {
- if (page.shapes[clone.id]) {
- throw Error("uh oh, we didn't clone correctly");
- }
- });
-
- this.cloneInfo = {
- state: 'ready',
- clones,
- cloneMap,
- clonedBindings,
- };
- };
-}
diff --git a/libs/components/board-sessions/tsconfig.json b/libs/components/board-sessions/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/board-sessions/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/board-sessions/tsconfig.lib.json b/libs/components/board-sessions/tsconfig.lib.json
deleted file mode 100644
index 6a4378740..000000000
--- a/libs/components/board-sessions/tsconfig.lib.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "jest.config.ts",
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/board-sessions/tsconfig.spec.json b/libs/components/board-sessions/tsconfig.spec.json
deleted file mode 100644
index 24b3ac659..000000000
--- a/libs/components/board-sessions/tsconfig.spec.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "jest.config.ts",
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/board-shapes/.babelrc b/libs/components/board-shapes/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/board-shapes/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/board-shapes/.eslintrc.json b/libs/components/board-shapes/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/board-shapes/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/board-shapes/README.md b/libs/components/board-shapes/README.md
deleted file mode 100644
index deb7f3af1..000000000
--- a/libs/components/board-shapes/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-board-shapes
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test components-board-shapes` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/board-shapes/jest.config.ts b/libs/components/board-shapes/jest.config.ts
deleted file mode 100644
index ec03e8dcd..000000000
--- a/libs/components/board-shapes/jest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'components-board-shapes',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/board-shapes',
-};
diff --git a/libs/components/board-shapes/package.json b/libs/components/board-shapes/package.json
deleted file mode 100644
index 20faf255e..000000000
--- a/libs/components/board-shapes/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "@toeverything/components/board-shapes",
- "version": "0.0.1",
- "license": "MIT",
- "dependencies": {
- "@tldraw/core": "^1.12.0",
- "@tldraw/intersect": "^1.7.1",
- "@tldraw/vec": "^1.7.0",
- "perfect-freehand": "^1.0.16"
- }
-}
diff --git a/libs/components/board-shapes/project.json b/libs/components/board-shapes/project.json
deleted file mode 100644
index bf2f7fb32..000000000
--- a/libs/components/board-shapes/project.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/components/board-shapes/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/board-shapes",
- "tsConfig": "libs/components/board-shapes/tsconfig.lib.json",
- "project": "libs/components/board-shapes/package.json",
- "entryFile": "libs/components/board-shapes/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/board-shapes/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/board-shapes/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/board-shapes"],
- "options": {
- "jestConfig": "libs/components/board-shapes/jest.config.ts",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/board-shapes/src/TDShapeUtil.tsx b/libs/components/board-shapes/src/TDShapeUtil.tsx
deleted file mode 100644
index bb8da6905..000000000
--- a/libs/components/board-shapes/src/TDShapeUtil.tsx
+++ /dev/null
@@ -1,251 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import type { TLBounds, TLPointerInfo } from '@tldraw/core';
-import { TLShapeUtil, Utils } from '@tldraw/core';
-import {
- intersectLineSegmentBounds,
- intersectLineSegmentPolyline,
- intersectRayBounds,
-} from '@tldraw/intersect';
-import { Vec } from '@tldraw/vec';
-import type {
- TDMeta,
- TDShape,
- TransformInfo,
-} from '@toeverything/components/board-types';
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-import { createRef } from 'react';
-import { getFontStyle, getShapeStyle } from './shared';
-import { getTextLabelSize } from './shared/get-text-size';
-import { getTextSvgElement } from './shared/get-text-svg-element';
-
-export abstract class TDShapeUtil<
- T extends TDShape,
- E extends Element = any
-> extends TLShapeUtil {
- abstract type: T['type'];
-
- canBind = false;
-
- canEdit = false;
-
- canClone = false;
-
- isAspectRatioLocked = false;
-
- hideResizeHandles = false;
-
- bindingDistance = BINDING_DISTANCE;
-
- abstract getShape: (props: Partial) => T;
-
- hitTestPoint = (shape: T, point: number[]): boolean => {
- return Utils.pointInBounds(point, this.getRotatedBounds(shape));
- };
-
- hitTestLineSegment = (shape: T, A: number[], B: number[]): boolean => {
- const box = Utils.getBoundsFromPoints([A, B]);
- const bounds = this.getBounds(shape);
-
- return Utils.boundsContain(bounds, box) || shape.rotation
- ? intersectLineSegmentPolyline(
- A,
- B,
- Utils.getRotatedCorners(this.getBounds(shape))
- ).didIntersect
- : intersectLineSegmentBounds(A, B, this.getBounds(shape)).length >
- 0;
- };
-
- create = (props: { id: string; workspace: string } & Partial) => {
- this.refMap.set(props.id, createRef());
- return this.getShape(props);
- };
-
- getCenter = (shape: T) => {
- return Utils.getBoundsCenter(this.getBounds(shape));
- };
-
- getExpandedBounds = (shape: T) => {
- return Utils.expandBounds(this.getBounds(shape), this.bindingDistance);
- };
-
- getBindingPoint = (
- shape: T,
- fromShape: K,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- // Algorithm time! We need to find the binding point (a normalized point inside of the shape, or around the shape, where the arrow will point to) and the distance from the binding shape to the anchor.
-
- const bounds = this.getBounds(shape);
- const expandedBounds = this.getExpandedBounds(shape);
-
- // The point must be inside of the expanded bounding box
- if (!Utils.pointInBounds(point, expandedBounds)) return;
-
- const intersections = intersectRayBounds(
- origin,
- direction,
- expandedBounds
- )
- .filter(int => int.didIntersect)
- .map(int => int.points[0]);
-
- if (!intersections.length) return;
-
- // The center of the shape
- const center = this.getCenter(shape);
-
- // Find furthest intersection between ray from origin through point and expanded bounds. TODO: What if the shape has a curve? In that case, should we intersect the circle-from-three-points instead?
- const intersection = intersections.sort(
- (a, b) => Vec.dist(b, origin) - Vec.dist(a, origin)
- )[0];
-
- // The point between the handle and the intersection
- const middlePoint = Vec.med(point, intersection);
-
- // The anchor is the point in the shape where the arrow will be pointing
- let anchor: number[];
-
- // The distance is the distance from the anchor to the handle
- let distance: number;
-
- if (bindAnywhere) {
- // If the user is indicating that they want to bind inside of the shape, we just use the handle's point
- anchor =
- Vec.dist(point, center) < BINDING_DISTANCE / 2 ? center : point;
- distance = 0;
- } else {
- if (
- Vec.distanceToLineSegment(point, middlePoint, center) <
- BINDING_DISTANCE / 2
- ) {
- // If the line segment would pass near to the center, snap the anchor the center point
- anchor = center;
- } else {
- // Otherwise, the anchor is the middle point between the handle and the intersection
- anchor = middlePoint;
- }
-
- if (Utils.pointInBounds(point, bounds)) {
- // If the point is inside of the shape, use the shape's binding distance
-
- distance = this.bindingDistance;
- } else {
- // Otherwise, use the actual distance from the handle point to nearest edge
- distance = Math.max(
- this.bindingDistance,
- Utils.getBoundsSides(bounds)
- .map(side =>
- Vec.distanceToLineSegment(
- side[1][0],
- side[1][1],
- point
- )
- )
- .sort((a, b) => a - b)[0]
- );
- }
- }
-
- // The binding point is a normalized point indicating the position of the anchor.
- // An anchor at the middle of the shape would be (0.5, 0.5). When the shape's bounds
- // changes, we will re-recalculate the actual anchor point by multiplying the
- // normalized point by the shape's new bounds.
- const bindingPoint = Vec.divV(
- Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
-
- return {
- point: Vec.clampV(bindingPoint, 0, 1),
- distance,
- };
- };
-
- mutate = (shape: T, props: Partial): Partial => {
- return props;
- };
-
- transform = (
- shape: T,
- bounds: TLBounds,
- info: TransformInfo
- ): Partial => {
- return { ...shape, point: [bounds.minX, bounds.minY] };
- };
-
- transformSingle = (
- shape: T,
- bounds: TLBounds,
- info: TransformInfo
- ): Partial | void => {
- return this.transform(shape, bounds, info);
- };
-
- updateChildren?: (
- shape: T,
- children: K[]
- ) => Partial[] | void;
-
- onChildrenChange?: (shape: T, children: TDShape[]) => Partial | void;
-
- onHandleChange?: (
- shape: T,
- handles: Partial
- ) => Partial | void;
-
- onRightPointHandle?: (
- shape: T,
- handles: Partial,
- info: Partial
- ) => Partial | void;
-
- onDoubleClickHandle?: (
- shape: T,
- handles: Partial,
- info: Partial
- ) => Partial | void;
-
- onDoubleClickBoundsHandle?: (shape: T) => Partial | void;
-
- onSessionComplete?: (shape: T) => Partial | void;
-
- getSvgElement = (shape: T, isDarkMode: boolean): SVGElement | void => {
- const elm = document
- .getElementById(shape.id + '_svg')
- ?.cloneNode(true) as SVGElement;
- if (!elm) return; // possibly in test mode
- if ('label' in shape && (shape as any).label) {
- const s = shape as TDShape & { label: string };
- const g = document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'g'
- );
- const bounds = this.getBounds(shape);
- const labelElm = getTextSvgElement(s['label'], shape.style, bounds);
- labelElm.setAttribute(
- 'fill',
- getShapeStyle(shape.style, isDarkMode).stroke
- );
- const font = getFontStyle(shape.style);
- const size = getTextLabelSize(s['label'], font);
- labelElm.setAttribute('transform-origin', 'top left');
- labelElm.setAttribute(
- 'transform',
- `translate(${bounds.width / 2}, ${
- (bounds.height - size[1]) / 2
- })`
- );
- g.setAttribute('text-align', 'center');
- g.setAttribute('text-anchor', 'middle');
- g.appendChild(elm);
- g.appendChild(labelElm);
- return g;
- }
- return elm;
- };
-}
diff --git a/libs/components/board-shapes/src/arrow-util/arrow-helpers.ts b/libs/components/board-shapes/src/arrow-util/arrow-helpers.ts
deleted file mode 100644
index 47916eeb6..000000000
--- a/libs/components/board-shapes/src/arrow-util/arrow-helpers.ts
+++ /dev/null
@@ -1,268 +0,0 @@
-import { Utils } from '@tldraw/core';
-import {
- intersectCircleCircle,
- intersectCircleLineSegment,
-} from '@tldraw/intersect';
-import Vec from '@tldraw/vec';
-import type {
- ArrowShape,
- Decoration,
- ShapeStyles,
-} from '@toeverything/components/board-types';
-import { EASINGS } from '@toeverything/components/board-types';
-import getStroke from 'perfect-freehand';
-import { getShapeStyle } from '../shared/shape-styles';
-
-export function getArrowArcPath(
- start: number[],
- end: number[],
- circle: number[],
- bend: number
-) {
- return [
- 'M',
- start[0],
- start[1],
- 'A',
- circle[2],
- circle[2],
- 0,
- 0,
- bend < 0 ? 0 : 1,
- end[0],
- end[1],
- ].join(' ');
-}
-
-export function getBendPoint(handles: ArrowShape['handles'], bend: number) {
- const { start, end } = handles;
-
- const dist = Vec.dist(start.point, end.point);
-
- const midPoint = Vec.med(start.point, end.point);
-
- const bendDist = (dist / 2) * bend;
-
- const u = Vec.uni(Vec.vec(start.point, end.point));
-
- const point = Vec.toFixed(
- Math.abs(bendDist) < 10
- ? midPoint
- : Vec.add(midPoint, Vec.mul(Vec.per(u), bendDist))
- );
-
- return point;
-}
-
-export function renderFreehandArrowShaft(
- id: string,
- style: ShapeStyles,
- start: number[],
- end: number[],
- decorationStart: Decoration | undefined,
- decorationEnd: Decoration | undefined
-) {
- const getRandom = Utils.rng(id);
- const strokeWidth = getShapeStyle(style).strokeWidth;
- const startPoint = decorationStart
- ? Vec.nudge(start, end, strokeWidth)
- : start;
- const endPoint = decorationEnd ? Vec.nudge(end, start, strokeWidth) : end;
- const stroke = getStroke([startPoint, endPoint], {
- size: strokeWidth,
- thinning: 0.618 + getRandom() * 0.2,
- easing: EASINGS.easeOutQuad,
- simulatePressure: true,
- streamline: 0,
- last: true,
- });
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-export function renderCurvedFreehandArrowShaft(
- id: string,
- style: ShapeStyles,
- start: number[],
- end: number[],
- decorationStart: Decoration | undefined,
- decorationEnd: Decoration | undefined,
- center: number[],
- radius: number,
- length: number,
- easing: (t: number) => number
-) {
- const getRandom = Utils.rng(id);
- const strokeWidth = getShapeStyle(style).strokeWidth;
- const startPoint = decorationStart
- ? Vec.rotWith(start, center, strokeWidth / length)
- : start;
- const endPoint = decorationEnd
- ? Vec.rotWith(end, center, -(strokeWidth / length))
- : end;
- const startAngle = Vec.angle(center, startPoint);
- const endAngle = Vec.angle(center, endPoint);
- const points: number[][] = [];
- const count = 8 + Math.floor((Math.abs(length) / 20) * 1 + getRandom() / 2);
- for (let i = 0; i < count; i++) {
- const t = easing(i / count);
- const angle = Utils.lerpAngles(startAngle, endAngle, t);
- points.push(Vec.toFixed(Vec.nudgeAtAngle(center, angle, radius)));
- }
- const stroke = getStroke([startPoint, ...points, endPoint], {
- size: 1 + strokeWidth,
- thinning: 0.618 + getRandom() * 0.2,
- easing: EASINGS.easeOutQuad,
- simulatePressure: false,
- streamline: 0,
- last: true,
- });
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-export function getCtp(start: number[], bend: number[], end: number[]) {
- return Utils.circleFromThreePoints(start, end, bend);
-}
-
-export function getCurvedArrowHeadPoints(
- A: number[],
- r1: number,
- C: number[],
- r2: number,
- sweep: boolean
-) {
- const ints = intersectCircleCircle(A, r1 * 0.618, C, r2).points;
- if (!ints) {
- console.warn('Could not find an intersection for the arrow head.');
- return { left: A, right: A };
- }
- const int = sweep ? ints[0] : ints[1];
- const left = int
- ? Vec.nudge(Vec.rotWith(int, A, Math.PI / 6), A, r1 * -0.382)
- : A;
- const right = int
- ? Vec.nudge(Vec.rotWith(int, A, -Math.PI / 6), A, r1 * -0.382)
- : A;
- return { left, right };
-}
-
-export function getStraightArrowHeadPoints(
- A: number[],
- B: number[],
- r: number
-) {
- const ints = intersectCircleLineSegment(A, r, A, B).points;
- if (!ints) {
- console.warn('Could not find an intersection for the arrow head.');
- return { left: A, right: A };
- }
- const int = ints[0];
- const left = int ? Vec.rotWith(int, A, Math.PI / 6) : A;
- const right = int ? Vec.rotWith(int, A, -Math.PI / 6) : A;
- return { left, right };
-}
-
-export function getCurvedArrowHeadPath(
- A: number[],
- r1: number,
- C: number[],
- r2: number,
- sweep: boolean
-) {
- const { left, right } = getCurvedArrowHeadPoints(A, r1, C, r2, sweep);
- return `M ${left} L ${A} ${right}`;
-}
-
-export function getStraightArrowHeadPath(A: number[], B: number[], r: number) {
- const { left, right } = getStraightArrowHeadPoints(A, B, r);
- return `M ${left} L ${A} ${right}`;
-}
-
-export function getArrowPath(
- style: ShapeStyles,
- start: number[],
- bend: number[],
- end: number[],
- decorationStart: Decoration | undefined,
- decorationEnd: Decoration | undefined
-) {
- const { strokeWidth } = getShapeStyle(style, false);
- const arrowDist = Vec.dist(start, end);
- const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8);
- const path: (string | number)[] = [];
- const isStraightLine = Vec.dist(bend, Vec.toFixed(Vec.med(start, end))) < 1;
- if (isStraightLine) {
- path.push(`M ${start} L ${end}`);
- if (decorationStart) {
- path.push(getStraightArrowHeadPath(start, end, arrowHeadLength));
- }
- if (decorationEnd) {
- path.push(getStraightArrowHeadPath(end, start, arrowHeadLength));
- }
- } else {
- const circle = getCtp(start, bend, end);
- const center = [circle[0], circle[1]];
- const radius = circle[2];
- const length = getArcLength(center, radius, start, end);
- path.push(
- `M ${start} A ${radius} ${radius} 0 0 ${
- length > 0 ? '1' : '0'
- } ${end}`
- );
- if (decorationStart)
- path.push(
- getCurvedArrowHeadPath(
- start,
- arrowHeadLength,
- center,
- radius,
- length < 0
- )
- );
- if (decorationEnd) {
- path.push(
- getCurvedArrowHeadPath(
- end,
- arrowHeadLength,
- center,
- radius,
- length >= 0
- )
- );
- }
- }
- return path.join(' ');
-}
-
-export function getArcPoints(start: number[], bend: number[], end: number[]) {
- if (Vec.dist2(bend, Vec.med(start, end)) <= 4) return [start, end];
- // The arc is curved; calculate twenty points along the arc
- const points: number[][] = [];
- const circle = getCtp(start, bend, end);
- const center = [circle[0], circle[1]];
- const radius = circle[2];
- const startAngle = Vec.angle(center, start);
- const endAngle = Vec.angle(center, end);
- for (let i = 1 / 20; i < 1; i += 1 / 20) {
- const angle = Utils.lerpAngles(startAngle, endAngle, i);
- points.push(Vec.nudgeAtAngle(center, angle, radius));
- }
- return points;
-}
-
-export function isAngleBetween(a: number, b: number, c: number): boolean {
- if (c === a || c === b) return true;
- const PI2 = Math.PI * 2;
- const AB = (b - a + PI2) % PI2;
- const AC = (c - a + PI2) % PI2;
- return AB <= Math.PI !== AC > AB;
-}
-
-export function getArcLength(
- C: number[],
- r: number,
- A: number[],
- B: number[]
-): number {
- const sweep = Utils.getSweep(C, A, B);
- return r * (2 * Math.PI) * (sweep / (2 * Math.PI));
-}
diff --git a/libs/components/board-shapes/src/arrow-util/arrow-util.tsx b/libs/components/board-shapes/src/arrow-util/arrow-util.tsx
deleted file mode 100644
index dace7eeb3..000000000
--- a/libs/components/board-shapes/src/arrow-util/arrow-util.tsx
+++ /dev/null
@@ -1,604 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectArcBounds,
- intersectLineSegmentBounds,
- intersectLineSegmentLineSegment,
-} from '@tldraw/intersect';
-import { Vec } from '@tldraw/vec';
-import {
- ArrowShape,
- DashStyle,
- Decoration,
- GHOSTED_OPACITY,
- TDMeta,
- TDShapeType,
- TransformInfo,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- getFontStyle,
- getShapeStyle,
- getTextLabelSize,
- LabelMask,
- TextLabel,
-} from '../shared';
-import { defaultStyle } from '../shared/shape-styles';
-import { TDShapeUtil } from '../TDShapeUtil';
-import {
- getArcLength,
- getArcPoints,
- getArrowPath,
- getBendPoint,
- getCtp,
- isAngleBetween,
-} from './arrow-helpers';
-import { CurvedArrow } from './components/curved-arrow';
-import { StraightArrow } from './components/straight-arrow';
-
-type T = ArrowShape;
-type E = HTMLDivElement;
-
-export class ArrowUtil extends TDShapeUtil {
- type = TDShapeType.Arrow as const;
-
- override hideBounds = false;
-
- override canEdit = true;
-
- pathCache = new WeakMap();
-
- getShape = (props: Partial): T => {
- return {
- id: 'id',
- type: TDShapeType.Arrow,
- name: 'Arrow',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- rotation: 0,
- bend: 0,
- handles: {
- start: {
- id: 'start',
- index: 0,
- point: [0, 0],
- canBind: true,
- ...props.handles?.start,
- },
- end: {
- id: 'end',
- index: 1,
- point: [1, 1],
- canBind: true,
- ...props.handles?.end,
- },
- bend: {
- id: 'bend',
- index: 2,
- point: [0.5, 0.5],
- ...props.handles?.bend,
- },
- },
- decorations: props.decorations ?? {
- end: Decoration.Arrow,
- },
- style: {
- ...defaultStyle,
- isFilled: false,
- ...props.style,
- },
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- ...props,
- };
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- isEditing,
- isGhost,
- meta,
- events,
- onShapeChange,
- onShapeBlur,
- },
- ref
- ) => {
- const {
- id,
- label = '',
- handles: { start, bend, end },
- decorations = {},
- style,
- } = shape;
- const isStraightLine =
- Vec.dist(
- bend.point,
- Vec.toFixed(Vec.med(start.point, end.point))
- ) < 1;
- const font = getFontStyle(style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const labelSize =
- label || isEditing ? getTextLabelSize(label, font) : [0, 0];
- const bounds = this.getBounds(shape);
- const dist = React.useMemo(() => {
- const { start, bend, end } = shape.handles;
- if (isStraightLine) return Vec.dist(start.point, end.point);
- const circle = getCtp(start.point, bend.point, end.point);
- const center = circle.slice(0, 2);
- const radius = circle[2];
- const length = getArcLength(
- center,
- radius,
- start.point,
- end.point
- );
- return Math.abs(length);
- }, [shape.handles]);
- const scale = Math.max(
- 0.5,
- Math.min(
- 1,
- Math.max(
- dist / (labelSize[1] + 128),
- dist / (labelSize[0] + 128)
- )
- )
- );
- const offset = React.useMemo(() => {
- const bounds = this.getBounds(shape);
- const offset = Vec.sub(
- shape.handles.bend.point,
- Vec.toFixed([bounds.width / 2, bounds.height / 2])
- );
- return offset;
- }, [shape, scale]);
- const handleLabelChange = React.useCallback(
- (label: string) => {
- onShapeChange?.({ id, label });
- },
- [onShapeChange]
- );
- const Component = isStraightLine ? StraightArrow : CurvedArrow;
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape, bounds }) => {
- const {
- style,
- decorations,
- label,
- handles: { start, bend, end },
- } = shape;
- const font = getFontStyle(style);
- const labelSize = label ? getTextLabelSize(label, font) : [0, 0];
- const isStraightLine =
- Vec.dist(bend.point, Vec.toFixed(Vec.med(start.point, end.point))) <
- 1;
- const dist = React.useMemo(() => {
- const { start, bend, end } = shape.handles;
- if (isStraightLine) return Vec.dist(start.point, end.point);
- const circle = getCtp(start.point, bend.point, end.point);
- const center = circle.slice(0, 2);
- const radius = circle[2];
- const length = getArcLength(center, radius, start.point, end.point);
- return Math.abs(length);
- }, [shape.handles]);
- const scale = Math.max(
- 0.5,
- Math.min(
- 1,
- Math.max(
- dist / (labelSize[1] + 128),
- dist / (labelSize[0] + 128)
- )
- )
- );
- const offset = React.useMemo(() => {
- const bounds = this.getBounds(shape);
- const offset = Vec.sub(shape.handles.bend.point, [
- bounds.width / 2,
- bounds.height / 2,
- ]);
- return offset;
- }, [shape, scale]);
- return (
- <>
-
-
- {label && (
-
- )}
- >
- );
- });
-
- getBounds = (shape: T) => {
- const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
- const {
- handles: { start, bend, end },
- } = shape;
- return Utils.getBoundsFromPoints(
- getArcPoints(start.point, bend.point, end.point)
- );
- });
- return Utils.translateBounds(bounds, shape.point);
- };
-
- override getRotatedBounds = (shape: T) => {
- const {
- handles: { start, bend, end },
- } = shape;
- let points = getArcPoints(start.point, bend.point, end.point);
- const { minX, minY, maxX, maxY } = Utils.getBoundsFromPoints(points);
- if (shape.rotation !== 0) {
- points = points.map(pt =>
- Vec.rotWith(
- pt,
- [(minX + maxX) / 2, (minY + maxY) / 2],
- shape.rotation || 0
- )
- );
- }
-
- return Utils.translateBounds(
- Utils.getBoundsFromPoints(points),
- shape.point
- );
- };
-
- override getCenter = (shape: T) => {
- const { start, end } = shape.handles;
- return Vec.add(shape.point, Vec.med(start.point, end.point));
- };
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.decorations !== prev.decorations ||
- next.handles !== prev.handles ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- override hitTestPoint = (shape: T, point: number[]): boolean => {
- const {
- handles: { start, bend, end },
- } = shape;
- const pt = Vec.sub(point, shape.point);
- const points = getArcPoints(start.point, bend.point, end.point);
- for (let i = 1; i < points.length; i++) {
- if (Vec.distanceToLineSegment(points[i - 1], points[i], pt) < 1) {
- return true;
- }
- }
- return false;
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- const {
- handles: { start, bend, end },
- } = shape;
- const ptA = Vec.sub(A, shape.point);
- const ptB = Vec.sub(B, shape.point);
- const points = getArcPoints(start.point, bend.point, end.point);
- for (let i = 1; i < points.length; i++) {
- if (
- intersectLineSegmentLineSegment(
- points[i - 1],
- points[i],
- ptA,
- ptB
- ).didIntersect
- ) {
- return true;
- }
- }
- return false;
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds) => {
- const { start, end, bend } = shape.handles;
- const sp = Vec.add(shape.point, start.point);
- const ep = Vec.add(shape.point, end.point);
- if (
- Utils.pointInBounds(sp, bounds) ||
- Utils.pointInBounds(ep, bounds)
- ) {
- return true;
- }
- if (Vec.isEqual(Vec.med(start.point, end.point), bend.point)) {
- return intersectLineSegmentBounds(sp, ep, bounds).length > 0;
- } else {
- const [cx, cy, r] = getCtp(start.point, bend.point, end.point);
- const cp = Vec.add(shape.point, [cx, cy]);
- return intersectArcBounds(cp, r, sp, ep, bounds).length > 0;
- }
- };
-
- override transform = (
- shape: T,
- bounds: TLBounds,
- { initialShape, scaleX, scaleY }: TransformInfo
- ): Partial => {
- const initialShapeBounds = this.getBounds(initialShape);
- const handles: (keyof T['handles'])[] = ['start', 'end'];
- const nextHandles = { ...initialShape.handles };
- handles.forEach(handle => {
- const [x, y] = nextHandles[handle].point;
- const nw = x / initialShapeBounds.width;
- const nh = y / initialShapeBounds.height;
- nextHandles[handle] = {
- ...nextHandles[handle],
- point: [
- bounds.width * (scaleX < 0 ? 1 - nw : nw),
- bounds.height * (scaleY < 0 ? 1 - nh : nh),
- ],
- };
- });
- const { start, bend, end } = nextHandles;
- const dist = Vec.dist(start.point, end.point);
- const midPoint = Vec.med(start.point, end.point);
- const bendDist = (dist / 2) * initialShape.bend;
- const u = Vec.uni(Vec.vec(start.point, end.point));
- const point = Vec.add(midPoint, Vec.mul(Vec.per(u), bendDist));
- nextHandles['bend'] = {
- ...bend,
- point: Vec.toFixed(Math.abs(bendDist) < 10 ? midPoint : point),
- };
- return {
- point: Vec.toFixed([bounds.minX, bounds.minY]),
- handles: nextHandles,
- };
- };
-
- override onDoubleClickHandle = (
- shape: T,
- handle: Partial
- ): Partial | void => {
- switch (handle) {
- case 'bend': {
- return {
- bend: 0,
- handles: {
- ...shape.handles,
- bend: {
- ...shape.handles.bend,
- point: getBendPoint(shape.handles, shape.bend),
- },
- },
- };
- }
- case 'start': {
- return {
- decorations: {
- ...shape.decorations,
- start: shape.decorations?.start
- ? undefined
- : Decoration.Arrow,
- },
- };
- }
- case 'end': {
- return {
- decorations: {
- ...shape.decorations,
- end: shape.decorations?.end
- ? undefined
- : Decoration.Arrow,
- },
- };
- }
- }
-
- return this;
- };
-
- override onHandleChange = (
- shape: T,
- handles: Partial
- ): Partial | void => {
- let nextHandles = Utils.deepMerge(
- shape.handles,
- handles
- );
- let nextBend = shape.bend;
-
- nextHandles = Utils.deepMerge(nextHandles, {
- start: {
- point: Vec.toFixed(nextHandles.start.point),
- },
- end: {
- point: Vec.toFixed(nextHandles.end.point),
- },
- });
-
- // This will produce NaN values
- if (Vec.isEqual(nextHandles.start.point, nextHandles.end.point)) return;
-
- // If the user is moving the bend handle, we want to move the bend point
- if ('bend' in handles) {
- const { start, end, bend } = nextHandles;
-
- const distance = Vec.dist(start.point, end.point);
- const midPoint = Vec.med(start.point, end.point);
- const angle = Vec.angle(start.point, end.point);
- const u = Vec.uni(Vec.vec(start.point, end.point));
-
- // Create a line segment perendicular to the line between the start and end points
- const ap = Vec.add(midPoint, Vec.mul(Vec.per(u), distance));
- const bp = Vec.sub(midPoint, Vec.mul(Vec.per(u), distance));
-
- const bendPoint = Vec.nearestPointOnLineSegment(
- ap,
- bp,
- bend.point,
- true
- );
-
- // Find the distance between the midpoint and the nearest point on the
- // line segment to the bend handle's dragged point
- const bendDist = Vec.dist(midPoint, bendPoint);
-
- // The shape's "bend" is the ratio of the bend to the distance between
- // the start and end points. If the bend is below a certain amount, the
- // bend should be zero.
- const realBend = bendDist / (distance / 2);
-
- nextBend = Utils.clamp(realBend, -0.99, 0.99);
-
- // If the point is to the left of the line segment, we make the bend
- // negative, otherwise it's positive.
- const angleToBend = Vec.angle(start.point, bendPoint);
-
- // If resulting bend is low enough that the handle will snap to center,
- // then also snap the bend to center
-
- if (Vec.isEqual(midPoint, getBendPoint(nextHandles, nextBend))) {
- nextBend = 0;
- } else if (isAngleBetween(angle, angle + Math.PI, angleToBend)) {
- // Otherwise, fix the bend direction
- nextBend *= -1;
- }
- }
-
- const nextShape = {
- point: shape.point,
- bend: nextBend,
- handles: {
- ...nextHandles,
- bend: {
- ...nextHandles.bend,
- point: getBendPoint(nextHandles, nextBend),
- },
- },
- };
-
- // Zero out the handles to prevent handles with negative points. If a handle's x or y
- // is below zero, we need to move the shape left or up to make it zero.
- const topLeft = shape.point;
-
- const nextBounds = this.getBounds({ ...nextShape } as ArrowShape);
-
- const offset = Vec.sub([nextBounds.minX, nextBounds.minY], topLeft);
-
- if (!Vec.isEqual(offset, [0, 0])) {
- Object.values(nextShape.handles).forEach(handle => {
- handle.point = Vec.toFixed(Vec.sub(handle.point, offset));
- });
- nextShape.point = Vec.toFixed(Vec.add(nextShape.point, offset));
- }
-
- return nextShape;
- };
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/arrow-util/components/arrow-head.tsx b/libs/components/board-shapes/src/arrow-util/components/arrow-head.tsx
deleted file mode 100644
index e574e500d..000000000
--- a/libs/components/board-shapes/src/arrow-util/components/arrow-head.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-export interface ArrowheadProps {
- left: number[];
- middle: number[];
- right: number[];
- stroke: string;
- strokeWidth: number;
-}
-
-export function Arrowhead({
- left,
- middle,
- right,
- stroke,
- strokeWidth,
-}: ArrowheadProps) {
- return (
-
-
-
-
- );
-}
diff --git a/libs/components/board-shapes/src/arrow-util/components/curved-arrow.tsx b/libs/components/board-shapes/src/arrow-util/components/curved-arrow.tsx
deleted file mode 100644
index 9eed25c43..000000000
--- a/libs/components/board-shapes/src/arrow-util/components/curved-arrow.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type {
- Decoration,
- ShapeStyles,
-} from '@toeverything/components/board-types';
-import { EASINGS } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getArcLength,
- getArrowArcPath,
- getCtp,
- getCurvedArrowHeadPoints,
- renderCurvedFreehandArrowShaft,
-} from '../arrow-helpers';
-import { Arrowhead } from './arrow-head';
-
-interface ArrowSvgProps {
- id: string;
- style: ShapeStyles;
- start: number[];
- bend: number[];
- end: number[];
- arrowBend: number;
- decorationStart: Decoration | undefined;
- decorationEnd: Decoration | undefined;
- isDarkMode: boolean;
- isDraw: boolean;
-}
-
-export const CurvedArrow = React.memo(function CurvedArrow({
- id,
- style,
- start,
- bend,
- end,
- arrowBend,
- decorationStart,
- decorationEnd,
- isDraw,
- isDarkMode,
-}: ArrowSvgProps) {
- const arrowDist = Vec.dist(start, end);
- if (arrowDist < 2) return null;
- const styles = getShapeStyle(style, isDarkMode);
- const { strokeWidth } = styles;
- const sw = 1 + strokeWidth * 1.618;
- // Calculate a path as a segment of a circle passing through the three points start, bend, and end
- const circle = getCtp(start, bend, end);
- const center = [circle[0], circle[1]];
- const radius = circle[2];
- const length = getArcLength(center, radius, start, end);
- const getRandom = Utils.rng(id);
- const easing =
- EASINGS[getRandom() > 0 ? 'easeInOutSine' : 'easeInOutCubic'];
- const path = isDraw
- ? renderCurvedFreehandArrowShaft(
- id,
- style,
- start,
- end,
- decorationStart,
- decorationEnd,
- center,
- radius,
- length,
- easing
- )
- : getArrowArcPath(start, end, circle, arrowBend);
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- Math.abs(length),
- sw,
- style.dash,
- 2,
- false
- );
- // Arrowheads
- const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8);
- const startArrowHead = decorationStart
- ? getCurvedArrowHeadPoints(
- start,
- arrowHeadLength,
- center,
- radius,
- length < 0
- )
- : null;
- const endArrowHead = decorationEnd
- ? getCurvedArrowHeadPoints(
- end,
- arrowHeadLength,
- center,
- radius,
- length >= 0
- )
- : null;
- return (
- <>
-
-
- {startArrowHead && (
-
- )}
- {endArrowHead && (
-
- )}
- >
- );
-});
diff --git a/libs/components/board-shapes/src/arrow-util/components/straight-arrow.tsx b/libs/components/board-shapes/src/arrow-util/components/straight-arrow.tsx
deleted file mode 100644
index 20d0d3c93..000000000
--- a/libs/components/board-shapes/src/arrow-util/components/straight-arrow.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type {
- Decoration,
- ShapeStyles,
-} from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getStraightArrowHeadPoints,
- renderFreehandArrowShaft,
-} from '../arrow-helpers';
-import { Arrowhead } from './arrow-head';
-
-interface ArrowSvgProps {
- id: string;
- style: ShapeStyles;
- start: number[];
- bend: number[];
- end: number[];
- arrowBend: number;
- decorationStart: Decoration | undefined;
- decorationEnd: Decoration | undefined;
- isDarkMode: boolean;
- isDraw: boolean;
-}
-
-export const StraightArrow = React.memo(function StraightArrow({
- id,
- style,
- start,
- end,
- decorationStart,
- decorationEnd,
- isDraw,
- isDarkMode,
-}: ArrowSvgProps) {
- const arrowDist = Vec.dist(start, end);
- if (arrowDist < 2) return null;
- const styles = getShapeStyle(style, isDarkMode);
- const { strokeWidth } = styles;
- const sw = 1 + strokeWidth * 1.618;
- // Path between start and end points
- const path = isDraw
- ? renderFreehandArrowShaft(
- id,
- style,
- start,
- end,
- decorationStart,
- decorationEnd
- )
- : 'M' + Vec.toFixed(start) + 'L' + Vec.toFixed(end);
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- arrowDist,
- strokeWidth * 1.618,
- style.dash,
- 2,
- false
- );
- // Arrowheads
- const arrowHeadLength = Math.min(arrowDist / 3, strokeWidth * 8);
- const startArrowHead = decorationStart
- ? getStraightArrowHeadPoints(start, end, arrowHeadLength)
- : null;
- const endArrowHead = decorationEnd
- ? getStraightArrowHeadPoints(end, start, arrowHeadLength)
- : null;
- return (
- <>
-
-
- {startArrowHead && (
-
- )}
- {endArrowHead && (
-
- )}
- >
- );
-});
diff --git a/libs/components/board-shapes/src/arrow-util/index.ts b/libs/components/board-shapes/src/arrow-util/index.ts
deleted file mode 100644
index 356c20b37..000000000
--- a/libs/components/board-shapes/src/arrow-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './arrow-util';
diff --git a/libs/components/board-shapes/src/draw-util/DrawUtil.tsx b/libs/components/board-shapes/src/draw-util/DrawUtil.tsx
deleted file mode 100644
index db36aa0ef..000000000
--- a/libs/components/board-shapes/src/draw-util/DrawUtil.tsx
+++ /dev/null
@@ -1,376 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectBoundsBounds,
- intersectBoundsPolyline,
- intersectLineSegmentBounds,
- intersectLineSegmentLineSegment,
-} from '@tldraw/intersect';
-import { Vec } from '@tldraw/vec';
-import {
- DashStyle,
- DrawShape,
- GHOSTED_OPACITY,
- TDMeta,
- TDShapeType,
- TransformInfo,
-} from '@toeverything/components/board-types';
-import * as React from 'react';
-import { defaultStyle, getShapeStyle } from '../shared/shape-styles';
-import { TDShapeUtil } from '../TDShapeUtil';
-import {
- getDrawStrokePathTDSnapshot,
- getFillPath,
- getSolidStrokePathTDSnapshot,
-} from './draw-helpers';
-
-type T = DrawShape;
-type E = SVGSVGElement;
-
-export class DrawUtil extends TDShapeUtil {
- type = TDShapeType.Draw as const;
-
- pointsBoundsCache = new WeakMap([]);
-
- shapeBoundsCache = new Map();
-
- rotatedCache = new WeakMap([]);
-
- pointCache: Record = {};
-
- override canClone = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Draw,
- name: 'Draw',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- rotation: 0,
- style: defaultStyle,
- points: [],
- isComplete: false,
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- ({ shape, meta, isSelected, isGhost, events }, ref) => {
- const { points, style, isComplete } = shape;
-
- const polygon_path_td_snapshot = React.useMemo(() => {
- return getFillPath(shape);
- }, [points, style.strokeWidth]);
-
- const path_td_snapshot = React.useMemo(() => {
- return style.dash === DashStyle.Draw
- ? getDrawStrokePathTDSnapshot(shape)
- : getSolidStrokePathTDSnapshot(shape);
- }, [points, style.strokeWidth, style.dash, isComplete]);
-
- const styles = getShapeStyle(style, meta.isDarkMode);
- const { stroke, fill, strokeWidth } = styles;
-
- // For very short lines, draw a point instead of a line
- const bounds = this.getBounds(shape);
-
- const verySmall =
- bounds.width <= strokeWidth / 2 &&
- bounds.height <= strokeWidth / 2;
-
- if (verySmall) {
- const sw = 1 + strokeWidth;
-
- return (
-
-
-
- );
- }
-
- const shouldFill =
- style.isFilled &&
- points.length > 3 &&
- Vec.dist(points[0], points[points.length - 1]) <
- strokeWidth * 2;
-
- if (shape.style.dash === DashStyle.Draw) {
- return (
-
-
-
- {shouldFill && (
-
- )}
-
-
-
- );
- }
-
- // For solid, dash and dotted lines, draw a regular stroke path
-
- const strokeDasharray = {
- [DashStyle.None]: 'none',
- [DashStyle.Draw]: 'none',
- [DashStyle.Solid]: `none`,
- [DashStyle.Dotted]: `0.1 ${strokeWidth * 4}`,
- [DashStyle.Dashed]: `${strokeWidth * 4} ${strokeWidth * 4}`,
- }[style.dash];
-
- const strokeDashoffset = {
- [DashStyle.None]: 'none',
- [DashStyle.Draw]: 'none',
- [DashStyle.Solid]: `none`,
- [DashStyle.Dotted]: `0`,
- [DashStyle.Dashed]: `0`,
- }[style.dash];
-
- const sw = 1 + strokeWidth * 1.5;
-
- return (
-
-
-
-
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { points } = shape;
-
- const path_td_snapshot = React.useMemo(() => {
- return getSolidStrokePathTDSnapshot(shape);
- }, [points]);
-
- const bounds = this.getBounds(shape);
-
- const verySmall = bounds.width < 4 && bounds.height < 4;
-
- if (verySmall) {
- return ;
- }
-
- return ;
- });
-
- override transform = (
- shape: T,
- bounds: TLBounds,
- { initialShape, scaleX, scaleY }: TransformInfo
- ): Partial => {
- const initialShapeBounds = Utils.getFromCache(
- this.boundsCache,
- initialShape,
- () => Utils.getBoundsFromPoints(initialShape.points)
- );
-
- const points = initialShape.points.map(([x, y, r]) => {
- return [
- bounds.width *
- (scaleX < 0 // * sin?
- ? 1 - x / initialShapeBounds.width
- : x / initialShapeBounds.width),
- bounds.height *
- (scaleY < 0 // * cos?
- ? 1 - y / initialShapeBounds.height
- : y / initialShapeBounds.height),
- r,
- ];
- });
-
- const newBounds = Utils.getBoundsFromPoints(shape.points);
-
- const point = Vec.sub(
- [bounds.minX, bounds.minY],
- [newBounds.minX, newBounds.minY]
- );
-
- return {
- points,
- point,
- };
- };
-
- getBounds = (shape: T) => {
- // The goal here is to avoid recalculating the bounds from the
- // points array, which is expensive. However, we still need a
- // new bounds if the point has changed, but we will reuse the
- // previous bounds-from-points result if we can.
-
- const pointsHaveChanged = !this.pointsBoundsCache.has(shape.points);
- const pointHasChanged = !(this.pointCache[shape.id] === shape.point);
-
- if (pointsHaveChanged) {
- // If the points have changed, then bust the points cache
- const bounds = Utils.getBoundsFromPoints(shape.points);
- this.pointsBoundsCache.set(shape.points, bounds);
- this.shapeBoundsCache.set(
- shape.id,
- Utils.translateBounds(bounds, shape.point)
- );
- this.pointCache[shape.id] = shape.point;
- } else if (pointHasChanged && !pointsHaveChanged) {
- // If the point have has changed, then bust the point cache
- this.pointCache[shape.id] = shape.point;
- this.shapeBoundsCache.set(
- shape.id,
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- Utils.translateBounds(
- this.pointsBoundsCache.get(shape.points)!,
- shape.point
- )
- );
- }
-
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- return this.shapeBoundsCache.get(shape.id)!;
- };
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.points !== prev.points ||
- next.style !== prev.style ||
- next.isComplete !== prev.isComplete
- );
- };
-
- override hitTestPoint = (shape: T, point: number[]) => {
- const ptA = Vec.sub(point, shape.point);
- return Utils.pointInPolyline(ptA, shape.points);
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- const { points, point } = shape;
- const ptA = Vec.sub(A, point);
- const ptB = Vec.sub(B, point);
- const bounds = this.getBounds(shape);
-
- if (points.length <= 2) {
- return Vec.distanceToLineSegment(A, B, shape.point) < 4;
- }
-
- if (intersectLineSegmentBounds(ptA, ptB, bounds)) {
- for (let i = 1; i < points.length; i++) {
- if (
- intersectLineSegmentLineSegment(
- points[i - 1],
- points[i],
- ptA,
- ptB
- ).didIntersect
- ) {
- return true;
- }
- }
- }
-
- return false;
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds) => {
- // Test axis-aligned shape
- if (!shape.rotation) {
- const shapeBounds = this.getBounds(shape);
-
- return (
- Utils.boundsContain(bounds, shapeBounds) ||
- ((Utils.boundsContain(shapeBounds, bounds) ||
- intersectBoundsBounds(shapeBounds, bounds).length > 0) &&
- intersectBoundsPolyline(
- Utils.translateBounds(bounds, Vec.neg(shape.point)),
- shape.points
- ).length > 0)
- );
- }
-
- // Test rotated shape
- const rBounds = this.getRotatedBounds(shape);
-
- const rotatedBounds = Utils.getFromCache(
- this.rotatedCache,
- shape,
- () => {
- const c = Utils.getBoundsCenter(
- Utils.getBoundsFromPoints(shape.points)
- );
- return shape.points.map(pt =>
- Vec.rotWith(pt, c, shape.rotation || 0)
- );
- }
- );
-
- return (
- Utils.boundsContain(bounds, rBounds) ||
- intersectBoundsPolyline(
- Utils.translateBounds(bounds, Vec.neg(shape.point)),
- rotatedBounds
- ).length > 0
- );
- };
-}
diff --git a/libs/components/board-shapes/src/draw-util/draw-helpers.ts b/libs/components/board-shapes/src/draw-util/draw-helpers.ts
deleted file mode 100644
index f0cb0d434..000000000
--- a/libs/components/board-shapes/src/draw-util/draw-helpers.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { DrawShape } from '@toeverything/components/board-types';
-import {
- getStrokeOutlinePoints,
- getStrokePoints,
- StrokeOptions,
-} from 'perfect-freehand';
-import { getShapeStyle } from '../shared/shape-styles';
-
-const simulatePressureSettings: StrokeOptions = {
- easing: t => Math.sin((t * Math.PI) / 2),
- simulatePressure: true,
-};
-
-const realPressureSettings: StrokeOptions = {
- easing: t => t * t,
- simulatePressure: false,
-};
-
-export function getFreehandOptions(shape: DrawShape) {
- const styles = getShapeStyle(shape.style);
-
- const options: StrokeOptions = {
- size: 1 + styles.strokeWidth * 1.5,
- thinning: 0.65,
- streamline: 0.65,
- smoothing: 0.65,
- ...(shape.points[1][2] === 0.5
- ? simulatePressureSettings
- : realPressureSettings),
- last: shape.isComplete,
- };
-
- return options;
-}
-
-export function getFillPath(shape: DrawShape) {
- if (shape.points.length < 2) return '';
-
- return Utils.getSvgPathFromStroke(
- getStrokePoints(shape.points, getFreehandOptions(shape)).map(
- pt => pt.point
- )
- );
-}
-
-export function getDrawStrokePoints(shape: DrawShape, options: StrokeOptions) {
- return getStrokePoints(shape.points, options);
-}
-
-/**
- * Get path data for a stroke with the DashStyle.Draw dash style.
- */
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getDrawStrokePathTDSnapshot(shape: DrawShape) {
- if (shape.points.length < 2) return '';
- const options = getFreehandOptions(shape);
- const strokePoints = getDrawStrokePoints(shape, options);
- const path = Utils.getSvgPathFromStroke(
- getStrokeOutlinePoints(strokePoints, options)
- );
- return path;
-}
-
-/**
- * Get SVG path data for a shape that has a DashStyle other than DashStyles.Draw.
- */
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getSolidStrokePathTDSnapshot(shape: DrawShape) {
- const { points } = shape;
- if (points.length < 2) return 'M 0 0 L 0 0';
- const options = getFreehandOptions(shape);
- const strokePoints = getDrawStrokePoints(shape, options).map(pt =>
- pt.point.slice(0, 2)
- );
- const last = points[points.length - 1].slice(0, 2);
- if (!Vec.isEqual(strokePoints[0], last)) strokePoints.push(last);
- const path = Utils.getSvgPathFromStroke(strokePoints, false);
- return path;
-}
diff --git a/libs/components/board-shapes/src/draw-util/index.ts b/libs/components/board-shapes/src/draw-util/index.ts
deleted file mode 100644
index f41fc7b42..000000000
--- a/libs/components/board-shapes/src/draw-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './DrawUtil';
diff --git a/libs/components/board-shapes/src/editor-util/EditorUtil.tsx b/libs/components/board-shapes/src/editor-util/EditorUtil.tsx
deleted file mode 100644
index 805ada062..000000000
--- a/libs/components/board-shapes/src/editor-util/EditorUtil.tsx
+++ /dev/null
@@ -1,296 +0,0 @@
-/* eslint-disable no-restricted-syntax */
-import { HTMLContainer, TLBounds, Utils } from '@tldraw/core';
-import { Vec } from '@tldraw/vec';
-import { AffineEditor } from '@toeverything/components/affine-editor';
-import {
- EditorShape,
- TDMeta,
- TDShapeType,
- TransformInfo,
-} from '@toeverything/components/board-types';
-import type { BlockEditor } from '@toeverything/components/editor-core';
-import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
-import { styled } from '@toeverything/components/ui';
-import type { MouseEvent, SyntheticEvent } from 'react';
-import { memo, useCallback, useEffect, useRef } from 'react';
-import {
- defaultTextStyle,
- getBoundsRectangle,
- getTextSvgElement,
-} from '../shared';
-import { getShapeStyle } from '../shared/shape-styles';
-import { TDShapeUtil } from '../TDShapeUtil';
-const MemoAffineEditor = memo(AffineEditor, (prev, next) => {
- return (
- prev.workspace === next.workspace &&
- prev.rootBlockId === next.rootBlockId
- );
-});
-
-type T = EditorShape;
-type E = HTMLDivElement;
-
-export class EditorUtil extends TDShapeUtil {
- type = TDShapeType.Editor as const;
-
- override canBind = true;
-
- override canEdit = true;
-
- override canClone = true;
-
- override showCloneHandles = true;
- /**
- * Prevent editor from being destroyed when moving out of viewport
- */
- override isStateful = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: props.id,
- type: TDShapeType.Editor,
- name: 'Editor',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [MIN_PAGE_WIDTH, 200],
- rotation: 0,
- style: defaultTextStyle,
- rootBlockId: props.rootBlockId,
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- ({ shape, meta: { app }, events, isEditing, onShapeChange }, ref) => {
- const containerRef = useRef();
- const editorRef = useRef();
- const {
- workspace,
- rootBlockId,
- size: [width, height],
- } = shape;
-
- const state = app.useStore();
- const { currentPageId } = state.appState;
- const { editingId } = state.document.pageStates[currentPageId];
- const { shapes } = state.document.pages[currentPageId];
- const editingText =
- editingId != null &&
- shapes[editingId].type === TDShapeType.Editor;
-
- const zoomLevel =
- state.document.pageStates[state.appState.currentPageId].camera
- .zoom;
-
- // TODO: useEvent
- const onResize = useRef((_: ResizeObserverEntry[]) => {});
-
- useEffect(() => {
- onResize.current = e => {
- const first = e[0];
- const bounds = first.contentRect;
- const realHeight = bounds.height;
- if (
- bounds.height !== 0 &&
- Math.abs(realHeight - height) > 1
- ) {
- onShapeChange({
- id: shape.id,
- size: [width, realHeight],
- });
- }
- };
- }, [height, onShapeChange, shape.id, width, zoomLevel]);
-
- useEffect(() => {
- if (containerRef.current) {
- const obv = new ResizeObserver(e => onResize.current(e));
-
- obv.observe(containerRef.current);
-
- return () => obv.disconnect();
- }
- }, [onShapeChange]);
-
- const stopPropagation = useCallback(
- (e: SyntheticEvent) => {
- if (isEditing) {
- e.stopPropagation();
- }
- },
- [isEditing]
- );
-
- const activateIfEditing = useCallback(() => {
- const shapes =
- state.document.pages[state.appState.currentPageId].shapes;
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1352417
- if (shapes[shape.id] != null) {
- return;
- }
- if (editingText && editingId !== shape.id) {
- app.setEditingText(shape.id);
- }
- }, [app, state, shape.id, editingText, editingId]);
-
- useEffect(() => {
- (async () => {
- if (isEditing) {
- const lastBlock =
- await editorRef.current.getLastBlock();
- editorRef.current.selectionManager.activeNodeByNodeId(
- lastBlock.id
- );
- }
- })();
- }, [isEditing]);
-
- const onMouseDown = useCallback(
- (e: MouseEvent) => {
- if (e.detail === 2) {
- app.setEditingText(shape.id);
- }
- },
- [app, shape.id]
- );
-
- return (
-
-
-
- {editingText ? null : }
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const {
- size: [width, height],
- } = shape;
-
- return (
-
- );
- });
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.rootBlockId !== prev.rootBlockId ||
- next.workspace !== prev.workspace
- );
- };
-
- override transform = (
- shape: T,
- bounds: TLBounds,
- { scaleX, scaleY, transformOrigin }: TransformInfo
- ): Partial => {
- const point = Vec.toFixed([
- bounds.minX +
- (bounds.width - shape.size[0]) *
- (scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]),
- bounds.minY +
- (bounds.height - shape.size[1]) *
- (scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
- ]);
-
- return {
- point,
- };
- };
-
- override transformSingle = (shape: T, bounds: TLBounds): Partial => {
- return {
- size: Vec.toFixed([
- bounds.width > MIN_PAGE_WIDTH ? MIN_PAGE_WIDTH : bounds.width,
- shape.size[1],
- ]),
- };
- };
-
- override getSvgElement = (
- shape: T,
- isDarkMode: boolean
- ): SVGElement | void => {
- const bounds = this.getBounds(shape);
- const textBounds = Utils.expandBounds(bounds, -PADDING);
- const textElm = getTextSvgElement(
- 'This feature is currently not supported',
- shape.style,
- textBounds
- );
- const style = getShapeStyle(shape.style, isDarkMode);
- textElm.setAttribute('fill', style.fill);
- textElm.setAttribute('transform', `translate(${PADDING}, ${PADDING})`);
-
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
- const rect = document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'rect'
- );
- rect.setAttribute('width', bounds.width + '');
- rect.setAttribute('height', bounds.height + '');
- rect.setAttribute('fill', style.fill);
- rect.setAttribute('rx', '3');
- rect.setAttribute('ry', '3');
-
- g.appendChild(rect);
- g.appendChild(textElm);
-
- return g;
- };
-}
-
-/* -------------------------------------------------- */
-/* Helpers */
-/* -------------------------------------------------- */
-
-const PADDING = 16;
-// const MIN_CONTAINER_HEIGHT = 200;
-
-const Container = styled('div')<{ editing: boolean }>(({ editing }) => ({
- pointerEvents: 'all',
- position: 'relative',
- width: '100%',
- userSelect: editing ? 'unset' : 'none',
-}));
-
-const Mask = styled('div')({
- position: 'absolute',
- top: 0,
- left: 0,
- bottom: 0,
- right: 0,
-});
diff --git a/libs/components/board-shapes/src/editor-util/index.ts b/libs/components/board-shapes/src/editor-util/index.ts
deleted file mode 100644
index bd16c33ed..000000000
--- a/libs/components/board-shapes/src/editor-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { EditorUtil } from './EditorUtil';
diff --git a/libs/components/board-shapes/src/ellipse-util/EllipseUtil.tsx b/libs/components/board-shapes/src/ellipse-util/EllipseUtil.tsx
deleted file mode 100644
index 1a2576009..000000000
--- a/libs/components/board-shapes/src/ellipse-util/EllipseUtil.tsx
+++ /dev/null
@@ -1,363 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectEllipseBounds,
- intersectLineSegmentEllipse,
- intersectRayEllipse,
-} from '@tldraw/intersect';
-import { Vec } from '@tldraw/vec';
-import {
- DashStyle,
- EllipseShape,
- GHOSTED_OPACITY,
- LABEL_POINT,
- TDMeta,
- TDShape,
- TDShapeType,
- TransformInfo,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getFontStyle,
- getShapeStyle,
- TextLabel,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { DashedEllipse } from './components/DashedEllipse';
-import { DrawEllipse } from './components/DrawEllipse';
-import { getEllipseIndicatorPath } from './ellipse-helpers';
-
-type T = EllipseShape;
-type E = HTMLDivElement;
-type M = TDMeta;
-
-export class EllipseUtil extends TDShapeUtil {
- type = TDShapeType.Ellipse as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Ellipse,
- name: 'Ellipse',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- radius: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- isGhost,
- isSelected,
- isBinding,
- isEditing,
- meta,
- bounds,
- events,
- onShapeChange,
- onShapeBlur,
- },
- ref
- ) => {
- const {
- id,
- radius,
- style,
- label = '',
- labelPoint = LABEL_POINT,
- } = shape;
- const font = getFontStyle(shape.style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const strokeWidth = styles.strokeWidth;
- const sw = 1 + strokeWidth * 1.618;
- const rx = Math.max(0, radius[0] - sw / 2);
- const ry = Math.max(0, radius[1] - sw / 2);
- const Component =
- style.dash === DashStyle.Draw ? DrawEllipse : DashedEllipse;
- const handleLabelChange = React.useCallback(
- (label: string) => onShapeChange?.({ id, label }),
- [onShapeChange]
- );
- return (
-
-
-
- {isBinding && (
-
- )}
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { id, radius, style } = shape;
- const styles = getShapeStyle(style);
- const strokeWidth = styles.strokeWidth;
- const sw = 1 + strokeWidth * 1.618;
- const rx = Math.max(0, radius[0] - sw / 2);
- const ry = Math.max(0, radius[1] - sw / 2);
- return style.dash === DashStyle.Draw ? (
-
- ) : (
-
- );
- });
-
- override hitTestPoint = (shape: T, point: number[]): boolean => {
- return (
- Utils.pointInBounds(point, this.getRotatedBounds(shape)) &&
- Utils.pointInEllipse(
- point,
- this.getCenter(shape),
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0
- )
- );
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- return intersectLineSegmentEllipse(
- A,
- B,
- this.getCenter(shape),
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0
- ).didIntersect;
- };
-
- getBounds = (shape: T) => {
- return Utils.getFromCache(this.boundsCache, shape, () => {
- return Utils.getRotatedEllipseBounds(
- shape.point[0],
- shape.point[1],
- shape.radius[0],
- shape.radius[1],
- 0
- );
- });
- };
-
- override getRotatedBounds = (shape: T): TLBounds => {
- return Utils.getRotatedEllipseBounds(
- shape.point[0],
- shape.point[1],
- shape.radius[0],
- shape.radius[1],
- shape.rotation
- );
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds): boolean => {
- const shapeBounds = this.getBounds(shape);
-
- return (
- Utils.boundsContained(shapeBounds, bounds) ||
- intersectEllipseBounds(
- this.getCenter(shape),
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0,
- bounds
- ).length > 0
- );
- };
-
- override shouldRender = (prev: T, next: T): boolean => {
- return (
- next.radius !== prev.radius ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- override getCenter = (shape: T): number[] => {
- return Vec.add(shape.point, shape.radius);
- };
-
- override getBindingPoint = (
- shape: T,
- fromShape: K,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- const expandedBounds = this.getExpandedBounds(shape);
- const center = this.getCenter(shape);
- let bindingPoint: number[];
- let distance: number;
- if (
- !Utils.pointInEllipse(
- point,
- center,
- shape.radius[0] + this.bindingDistance,
- shape.radius[1] + this.bindingDistance
- )
- ) {
- return;
- }
- if (bindAnywhere) {
- if (Vec.dist(point, this.getCenter(shape)) < 12) {
- bindingPoint = [0.5, 0.5];
- } else {
- bindingPoint = Vec.divV(
- Vec.sub(point, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
- }
- distance = 0;
- } else {
- let intersection = intersectRayEllipse(
- origin,
- direction,
- center,
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0
- ).points.sort(
- (a, b) => Vec.dist(a, origin) - Vec.dist(b, origin)
- )[0];
- if (!intersection) {
- intersection = intersectLineSegmentEllipse(
- point,
- center,
- center,
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0
- ).points.sort(
- (a, b) => Vec.dist(a, point) - Vec.dist(b, point)
- )[0];
- }
- if (!intersection) {
- return undefined;
- }
- // The anchor is a point between the handle and the intersection
- const anchor = Vec.med(point, intersection);
- if (
- Vec.distanceToLineSegment(
- point,
- anchor,
- this.getCenter(shape)
- ) < 12
- ) {
- // If we're close to the center, snap to the center
- bindingPoint = [0.5, 0.5];
- } else {
- // Or else calculate a normalized point
- bindingPoint = Vec.divV(
- Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
- }
- if (
- Utils.pointInEllipse(
- point,
- center,
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0
- )
- ) {
- // Pad the arrow out by 16 points
- distance = this.bindingDistance / 2;
- } else {
- // Find the distance between the point and the ellipse
- const innerIntersection = intersectLineSegmentEllipse(
- point,
- center,
- center,
- shape.radius[0],
- shape.radius[1],
- shape.rotation || 0
- ).points[0];
- if (!innerIntersection) return undefined;
- distance = Math.max(
- this.bindingDistance / 2,
- Vec.dist(point, innerIntersection)
- );
- }
- }
- return {
- point: bindingPoint,
- distance,
- };
- };
-
- override transform = (
- shape: T,
- bounds: TLBounds,
- { scaleX, scaleY, initialShape }: TransformInfo
- ): Partial => {
- const { rotation = 0 } = initialShape;
- return {
- point: [bounds.minX, bounds.minY],
- radius: [bounds.width / 2, bounds.height / 2],
- rotation:
- (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
- ? -(rotation || 0)
- : rotation || 0,
- };
- };
-
- override transformSingle = (shape: T, bounds: TLBounds): Partial => {
- return {
- point: Vec.toFixed([bounds.minX, bounds.minY]),
- radius: Vec.div([bounds.width, bounds.height], 2),
- };
- };
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/ellipse-util/components/DashedEllipse.tsx b/libs/components/board-shapes/src/ellipse-util/components/DashedEllipse.tsx
deleted file mode 100644
index e24c01762..000000000
--- a/libs/components/board-shapes/src/ellipse-util/components/DashedEllipse.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Utils } from '@tldraw/core';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-
-interface EllipseSvgProps {
- radius: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DashedEllipse = React.memo(function DashedEllipse({
- radius,
- style,
- isSelected,
- isDarkMode,
-}: EllipseSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const sw = 1 + strokeWidth * 1.618;
- const rx = Math.max(0, radius[0] - sw / 2);
- const ry = Math.max(0, radius[1] - sw / 2);
- const perimeter = Utils.perimeterOfEllipse(rx, ry);
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- perimeter < 64 ? perimeter * 2 : perimeter,
- strokeWidth * 1.618,
- style.dash,
- 4
- );
-
- return (
- <>
-
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/ellipse-util/components/DrawEllipse.tsx b/libs/components/board-shapes/src/ellipse-util/components/DrawEllipse.tsx
deleted file mode 100644
index 7d1f9f4e7..000000000
--- a/libs/components/board-shapes/src/ellipse-util/components/DrawEllipse.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import { getEllipseIndicatorPath, getEllipsePath } from '../ellipse-helpers';
-
-interface EllipseSvgProps {
- id: string;
- radius: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DrawEllipse = React.memo(function DrawEllipse({
- id,
- radius,
- style,
- isSelected,
- isDarkMode,
-}: EllipseSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const innerPath = getEllipsePath(id, radius, style);
-
- return (
- <>
-
- {style.isFilled && (
-
- )}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/ellipse-util/ellipse-helpers.ts b/libs/components/board-shapes/src/ellipse-util/ellipse-helpers.ts
deleted file mode 100644
index dcb2dcab5..000000000
--- a/libs/components/board-shapes/src/ellipse-util/ellipse-helpers.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Utils } from '@tldraw/core';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import { EASINGS } from '@toeverything/components/board-types';
-import { getStrokeOutlinePoints, getStrokePoints } from 'perfect-freehand';
-import { getShapeStyle } from '../shared/shape-styles';
-
-export function getEllipseStrokePoints(
- id: string,
- radius: number[],
- style: ShapeStyles
-) {
- const { strokeWidth } = getShapeStyle(style);
- const getRandom = Utils.rng(id);
- const rx = radius[0] + getRandom() * strokeWidth * 2;
- const ry = radius[1] + getRandom() * strokeWidth * 2;
- const perimeter = Utils.perimeterOfEllipse(rx, ry);
- const points: number[][] = [];
- const start = Math.PI + Math.PI * getRandom();
- const extra = Math.abs(getRandom());
- const count = Math.max(16, perimeter / 10);
- for (let i = 0; i < count; i++) {
- const t = EASINGS.easeInOutSine(i / (count + 1));
- const rads = start * 2 + Math.PI * (2 + extra) * t;
- const c = Math.cos(rads);
- const s = Math.sin(rads);
- points.push([
- rx * c + radius[0],
- ry * s + radius[1],
- t + 0.5 + getRandom() / 2,
- ]);
- }
- return getStrokePoints(points, {
- size: 1 + strokeWidth * 2,
- thinning: 0.618,
- end: { taper: perimeter / 8 },
- start: { taper: perimeter / 12 },
- streamline: 0,
- simulatePressure: true,
- });
-}
-
-export function getEllipsePath(
- id: string,
- radius: number[],
- style: ShapeStyles
-) {
- const { strokeWidth } = getShapeStyle(style);
- const getRandom = Utils.rng(id);
- const rx = radius[0] + getRandom() * strokeWidth * 2;
- const ry = radius[1] + getRandom() * strokeWidth * 2;
- const perimeter = Utils.perimeterOfEllipse(rx, ry);
- return Utils.getSvgPathFromStroke(
- getStrokeOutlinePoints(getEllipseStrokePoints(id, radius, style), {
- size: 2 + strokeWidth * 2,
- thinning: 0.618,
- end: { taper: perimeter / 8 },
- start: { taper: perimeter / 12 },
- streamline: 0,
- simulatePressure: true,
- })
- );
-}
-
-export function getEllipseIndicatorPath(
- id: string,
- radius: number[],
- style: ShapeStyles
-) {
- return Utils.getSvgPathFromStroke(
- getEllipseStrokePoints(id, radius, style).map(pt =>
- pt.point.slice(0, 2)
- ),
- false
- );
-}
diff --git a/libs/components/board-shapes/src/ellipse-util/index.ts b/libs/components/board-shapes/src/ellipse-util/index.ts
deleted file mode 100644
index d13d1a8e4..000000000
--- a/libs/components/board-shapes/src/ellipse-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './EllipseUtil';
diff --git a/libs/components/board-shapes/src/frame-util/FrameUtil.tsx b/libs/components/board-shapes/src/frame-util/FrameUtil.tsx
deleted file mode 100644
index 22710dc9b..000000000
--- a/libs/components/board-shapes/src/frame-util/FrameUtil.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-/* eslint-disable no-restricted-syntax */
-import { SVGContainer, Utils } from '@tldraw/core';
-import {
- FrameShape,
- TDMeta,
- TDShapeType,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import {
- defaultStyle,
- getBoundsRectangle,
- getShapeStyle,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { Frame } from './components/Frame';
-
-type T = FrameShape;
-type E = SVGSVGElement;
-
-export class FrameUtil extends TDShapeUtil {
- type = TDShapeType.Frame as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Frame,
- name: 'Frame',
- parentId: 'page',
- childIndex: 0,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- isSelected,
- meta,
- bounds,
- events,
- onShapeBlur,
- onShapeChange,
- },
- ref
- ) => {
- const { id, size, style } = shape;
- return (
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { id, style, size } = shape;
-
- const styles = getShapeStyle(style, false);
- const sw = styles.strokeWidth;
- return (
-
- );
- });
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const FullWrapper = styled('div')({
- width: '100%',
- height: '100%',
-});
diff --git a/libs/components/board-shapes/src/frame-util/components/Frame.tsx b/libs/components/board-shapes/src/frame-util/components/Frame.tsx
deleted file mode 100644
index 57736495d..000000000
--- a/libs/components/board-shapes/src/frame-util/components/Frame.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-
-interface RectangleSvgProps {
- id: string;
- style: ShapeStyles;
- isSelected: boolean;
- size: number[];
- isDarkMode: boolean;
-}
-
-export const Frame = React.memo(function DashedRectangle({
- id,
- style,
- size,
- isSelected,
- isDarkMode,
-}: RectangleSvgProps) {
- const { strokeWidth, fill } = getShapeStyle(style, isDarkMode);
-
- const _fill = fill && fill !== 'none' ? fill : '#F7F9FF';
-
- const sw = 1 + strokeWidth * 1.618;
-
- const w = Math.max(0, size[0] - sw / 2);
- const h = Math.max(0, size[1] - sw / 2);
-
- return (
- <>
-
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/frame-util/components/frame-binding-indicator.tsx b/libs/components/board-shapes/src/frame-util/components/frame-binding-indicator.tsx
deleted file mode 100644
index ee349f163..000000000
--- a/libs/components/board-shapes/src/frame-util/components/frame-binding-indicator.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-
-interface BindingIndicatorProps {
- strokeWidth: number;
- size: number[];
-}
-export function BindingIndicator({ strokeWidth, size }: BindingIndicatorProps) {
- return (
-
- );
-}
diff --git a/libs/components/board-shapes/src/frame-util/index.ts b/libs/components/board-shapes/src/frame-util/index.ts
deleted file mode 100644
index 5401a5fd2..000000000
--- a/libs/components/board-shapes/src/frame-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './FrameUtil';
diff --git a/libs/components/board-shapes/src/group-util/group-util.tsx b/libs/components/board-shapes/src/group-util/group-util.tsx
deleted file mode 100644
index 07b8d2a6f..000000000
--- a/libs/components/board-shapes/src/group-util/group-util.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-import { SVGContainer, Utils } from '@tldraw/core';
-import {
- GHOSTED_OPACITY,
- GroupShape,
- TDMeta,
- TDShapeType,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import { commonColors, getBoundsRectangle } from '../shared';
-import { defaultStyle } from '../shared/shape-styles';
-import { TDShapeUtil } from '../TDShapeUtil';
-
-type T = GroupShape;
-type E = SVGSVGElement;
-
-export class GroupUtil extends TDShapeUtil {
- type = TDShapeType.Group as const;
-
- override canBind = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Group,
- name: 'Group',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [100, 100],
- rotation: 0,
- children: [],
- style: defaultStyle,
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- ({ shape, isBinding, isGhost, isHovered, isSelected, events }, ref) => {
- const { id, size } = shape;
-
- const sw = 2;
- const w = Math.max(0, size[0] - sw / 2);
- const h = Math.max(0, size[1] - sw / 2);
-
- const strokes: [number[], number[], number][] = [
- [[sw / 2, sw / 2], [w, sw / 2], w - sw / 2],
- [[w, sw / 2], [w, h], h - sw / 2],
- [[w, h], [sw / 2, h], w - sw / 2],
- [[sw / 2, h], [sw / 2, sw / 2], h - sw / 2],
- ];
-
- const paths = strokes.map(([start, end], i) => {
- return (
-
- );
- });
-
- return (
-
- {isBinding && (
-
- )}
-
-
-
- {paths}
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { id, size } = shape;
-
- const sw = 2;
- const w = Math.max(0, size[0] - sw / 2);
- const h = Math.max(0, size[1] - sw / 2);
-
- const strokes: [number[], number[], number][] = [
- [[sw / 2, sw / 2], [w, sw / 2], w - sw / 2],
- [[w, sw / 2], [w, h], h - sw / 2],
- [[w, h], [sw / 2, h], w - sw / 2],
- [[sw / 2, h], [sw / 2, sw / 2], h - sw / 2],
- ];
-
- const paths = strokes.map(([start, end], i) => {
- return (
-
- );
- });
-
- return (
-
- {paths}
-
- );
- });
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override shouldRender = (prev: T, next: T) => {
- return next.size !== prev.size || next.style !== prev.style;
- };
-}
-
-const ScaledLines = styled('g')({
- strokeWidth: 'calc(1.5px * var(--tl-scale))',
- strokeDasharray: `calc(1px * var(--tl-scale)), calc(3px * var(--tl-scale))`,
-});
diff --git a/libs/components/board-shapes/src/group-util/index.ts b/libs/components/board-shapes/src/group-util/index.ts
deleted file mode 100644
index bc1bf8a44..000000000
--- a/libs/components/board-shapes/src/group-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './group-util';
diff --git a/libs/components/board-shapes/src/hexagon-util/HexagonUtil.tsx b/libs/components/board-shapes/src/hexagon-util/HexagonUtil.tsx
deleted file mode 100644
index dd32d6105..000000000
--- a/libs/components/board-shapes/src/hexagon-util/HexagonUtil.tsx
+++ /dev/null
@@ -1,294 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectBoundsPolygon,
- intersectLineSegmentPolyline,
- intersectRayLineSegment,
-} from '@tldraw/intersect';
-import Vec from '@tldraw/vec';
-import {
- BINDING_DISTANCE,
- DashStyle,
- GHOSTED_OPACITY,
- HexagonShape,
- LABEL_POINT,
- TDMeta,
- TDShape,
- TDShapeType,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- getFontStyle,
- getShapeStyle,
- TextLabel,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { DashedHexagon } from './components/DashedHexagon';
-import { DrawHexagon } from './components/DrawHexagon';
-import { HexagonBindingIndicator } from './components/HexagonBindingIndicator';
-import { getHexagonCentroid, getHexagonPoints } from './hexagon-helpers';
-
-type T = HexagonShape;
-type E = HTMLDivElement;
-
-export class HexagonUtil extends TDShapeUtil {
- type = TDShapeType.Hexagon as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Hexagon,
- name: 'Triangle',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- bounds,
- isBinding,
- isEditing,
- isSelected,
- isGhost,
- meta,
- events,
- onShapeChange,
- onShapeBlur,
- },
- ref
- ) => {
- const {
- id,
- label = '',
- size,
- style,
- labelPoint = LABEL_POINT,
- } = shape;
- const font = getFontStyle(style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const Component =
- style.dash === DashStyle.Draw ? DrawHexagon : DashedHexagon;
- const handleLabelChange = React.useCallback(
- (label: string) => onShapeChange?.({ id, label }),
- [onShapeChange]
- );
- const offsetY = React.useMemo(() => {
- const center = Vec.div(size, 2);
- const centroid = getHexagonCentroid(size);
- return (centroid[1] - center[1]) * 0.72;
- }, [size]);
- return (
-
-
-
- {isBinding && }
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { size } = shape;
- return ;
- });
-
- private get_points(shape: T) {
- const {
- rotation = 0,
- point: [x, y],
- size: [w, h],
- } = shape;
- return [
- [x + w / 2, y],
- [x, y + h],
- [x + w, y + h],
- ].map(pt => Vec.rotWith(pt, this.getCenter(shape), rotation));
- }
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override getExpandedBounds = (shape: T) => {
- return Utils.getBoundsFromPoints(
- getHexagonPoints(shape.size, this.bindingDistance).map(pt =>
- Vec.add(pt, shape.point)
- )
- );
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- return intersectLineSegmentPolyline(A, B, this.get_points(shape))
- .didIntersect;
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds): boolean => {
- return (
- Utils.boundsContained(this.getBounds(shape), bounds) ||
- intersectBoundsPolygon(bounds, this.get_points(shape)).length > 0
- );
- };
-
- override getBindingPoint = (
- shape: T,
- fromShape: K,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- // Algorithm time! We need to find the binding point (a normalized point inside of the shape, or around the shape, where the arrow will point to) and the distance from the binding shape to the anchor.
-
- const expandedBounds = this.getExpandedBounds(shape);
-
- if (!Utils.pointInBounds(point, expandedBounds)) return;
-
- const points = getHexagonPoints(shape.size).map(pt =>
- Vec.add(pt, shape.point)
- );
-
- const expandedPoints = getHexagonPoints(
- shape.size,
- this.bindingDistance
- ).map(pt => Vec.add(pt, shape.point));
-
- const closestDistanceToEdge = Utils.pointsToLineSegments(points, true)
- .map(([a, b]) => Vec.distanceToLineSegment(a, b, point))
- .sort((a, b) => a - b)[0];
-
- if (
- !(
- Utils.pointInPolygon(point, expandedPoints) ||
- closestDistanceToEdge < this.bindingDistance
- )
- )
- return;
-
- const intersections = Utils.pointsToLineSegments(
- expandedPoints.concat([expandedPoints[0]])
- )
- .map(segment =>
- intersectRayLineSegment(
- origin,
- direction,
- segment[0],
- segment[1]
- )
- )
- .filter(intersection => intersection.didIntersect)
- .flatMap(intersection => intersection.points);
-
- if (!intersections.length) return;
-
- // The center of the triangle
- const center = Vec.add(getHexagonCentroid(shape.size), shape.point);
-
- // Find furthest intersection between ray from origin through point and expanded bounds. TODO: What if the shape has a curve? In that case, should we intersect the circle-from-three-points instead?
- const intersection = intersections.sort(
- (a, b) => Vec.dist(b, origin) - Vec.dist(a, origin)
- )[0];
-
- // The point between the handle and the intersection
- const middlePoint = Vec.med(point, intersection);
-
- let anchor: number[];
- let distance: number;
-
- if (bindAnywhere) {
- anchor =
- Vec.dist(point, center) < BINDING_DISTANCE / 2 ? center : point;
- distance = 0;
- } else {
- if (
- Vec.distanceToLineSegment(point, middlePoint, center) <
- BINDING_DISTANCE / 2
- ) {
- anchor = center;
- } else {
- anchor = middlePoint;
- }
-
- if (Utils.pointInPolygon(point, points)) {
- distance = this.bindingDistance;
- } else {
- distance = Math.max(
- this.bindingDistance,
- closestDistanceToEdge
- );
- }
- }
-
- const bindingPoint = Vec.divV(
- Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
-
- return {
- point: Vec.clampV(bindingPoint, 0, 1),
- distance,
- };
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/hexagon-util/components/DashedHexagon.tsx b/libs/components/board-shapes/src/hexagon-util/components/DashedHexagon.tsx
deleted file mode 100644
index 4080ced2c..000000000
--- a/libs/components/board-shapes/src/hexagon-util/components/DashedHexagon.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import { getHexagonPoints } from '../hexagon-helpers';
-
-interface HexagonSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DashedHexagon = React.memo(function DashedHexagon({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: HexagonSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const sw = 1 + strokeWidth * 1.618;
- const points = getHexagonPoints(size);
- const sides = Utils.pointsToLineSegments(points, true);
- const paths = sides.map(([start, end], i) => {
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- Vec.dist(start, end),
- strokeWidth * 1.618,
- style.dash
- );
-
- return (
-
- );
- });
-
- const bgPath = points.join();
-
- return (
- <>
-
- {style.isFilled && (
-
- )}
- {paths}
- >
- );
-});
diff --git a/libs/components/board-shapes/src/hexagon-util/components/DrawHexagon.tsx b/libs/components/board-shapes/src/hexagon-util/components/DrawHexagon.tsx
deleted file mode 100644
index 90018e312..000000000
--- a/libs/components/board-shapes/src/hexagon-util/components/DrawHexagon.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getHexagonIndicatorPathTDSnapshot,
- getHexagonPath,
-} from '../hexagon-helpers';
-
-interface HexagonSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DrawHexagon = React.memo(function DrawTriangle({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: HexagonSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const path_td_snapshot = getHexagonPath(id, size, style);
- const indicatorPath = getHexagonIndicatorPathTDSnapshot(id, size, style);
- return (
- <>
-
- {style.isFilled && (
-
- )}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/hexagon-util/components/HexagonBindingIndicator.tsx b/libs/components/board-shapes/src/hexagon-util/components/HexagonBindingIndicator.tsx
deleted file mode 100644
index 9ea774666..000000000
--- a/libs/components/board-shapes/src/hexagon-util/components/HexagonBindingIndicator.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-import { getHexagonPoints } from '../hexagon-helpers';
-
-interface TriangleBindingIndicatorProps {
- size: number[];
-}
-
-export function HexagonBindingIndicator({
- size,
-}: TriangleBindingIndicatorProps) {
- const trianglePoints = getHexagonPoints(size).join();
- return (
-
- );
-}
diff --git a/libs/components/board-shapes/src/hexagon-util/hexagon-helpers.ts b/libs/components/board-shapes/src/hexagon-util/hexagon-helpers.ts
deleted file mode 100644
index 77a17b32d..000000000
--- a/libs/components/board-shapes/src/hexagon-util/hexagon-helpers.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import getStroke, { getStrokePoints } from 'perfect-freehand';
-import { getOffsetPolygon, getShapeStyle } from '../shared';
-function getPonits(w: number, h: number) {
- return [
- [w / 5, 0],
- [(w / 5) * 4, 0],
- [w, h / 2],
- [(w / 5) * 4, h],
- [w / 5, h],
- [0, h / 2],
- ];
-}
-
-export function getHexagonPoints(size: number[], offset = 0, rotation = 0) {
- const [w, h] = size;
- let points = getPonits(w, h);
- if (offset) points = getOffsetPolygon(points, offset);
- if (rotation)
- points = points.map(pt => Vec.rotWith(pt, [w / 2, h / 2], rotation));
-
- return points;
-}
-
-export function getHexagonCentroid(size: number[]) {
- const [w, h] = size;
- const points = getPonits(w, h);
- return [
- (points[0][0] + points[1][0] + points[2][0]) / 3,
- (points[0][1] + points[1][1] + points[2][1]) / 3,
- ];
-}
-
-function getHexagonDrawPoints(id: string, size: number[], strokeWidth: number) {
- const [w, h] = size;
- const getRandom = Utils.rng(id);
- // Random corner offsets
- const offsets = Array.from(Array(6)).map(() => {
- return [
- getRandom() * strokeWidth * 0.75,
- getRandom() * strokeWidth * 0.75,
- ];
- });
- // Corners
- const point = getPonits(w, h);
- const corners = [
- Vec.add(point[0], offsets[0]),
- Vec.add(point[1], offsets[1]),
- Vec.add(point[2], offsets[2]),
- Vec.add(point[3], offsets[3]),
- Vec.add(point[4], offsets[4]),
- Vec.add(point[5], offsets[5]),
- ];
-
- // Which side to start drawing first
- const rm = Math.round(Math.abs(getRandom() * 2 * 3));
- // Number of points per side
- // Inset each line by the corner radii and let the freehand algo
- // interpolate points for the corners.
- const lines = Utils.rotateArray(
- [
- Vec.pointsBetween(corners[0], corners[1], 32),
- Vec.pointsBetween(corners[1], corners[2], 32),
- Vec.pointsBetween(corners[2], corners[3], 32),
- Vec.pointsBetween(corners[3], corners[4], 32),
- Vec.pointsBetween(corners[4], corners[5], 32),
- Vec.pointsBetween(corners[5], corners[0], 32),
- ],
- rm
- );
- // For the final points, include the first half of the first line again,
- // so that the line wraps around and avoids ending on a sharp corner.
- // This has a bit of finesse and magic—if you change the points between
- // function, then you'll likely need to change this one too.
- const points = [...lines.flat(), ...lines[0]];
- return {
- points,
- };
-}
-
-function getDrawStrokeInfo(id: string, size: number[], style: ShapeStyles) {
- const { strokeWidth } = getShapeStyle(style);
- const { points } = getHexagonDrawPoints(id, size, strokeWidth);
- const options = {
- size: strokeWidth,
- thinning: 0.65,
- streamline: 0.3,
- smoothing: 1,
- simulatePressure: false,
- last: true,
- };
- return { points, options };
-}
-
-export function getHexagonPath(id: string, size: number[], style: ShapeStyles) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const stroke = getStroke(points, options);
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getHexagonIndicatorPathTDSnapshot(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const strokePoints = getStrokePoints(points, options);
- return Utils.getSvgPathFromStroke(
- strokePoints.map(pt => pt.point.slice(0, 2)),
- false
- );
-}
diff --git a/libs/components/board-shapes/src/hexagon-util/index.ts b/libs/components/board-shapes/src/hexagon-util/index.ts
deleted file mode 100644
index 344f7cf84..000000000
--- a/libs/components/board-shapes/src/hexagon-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './HexagonUtil';
diff --git a/libs/components/board-shapes/src/image-util/image-util.tsx b/libs/components/board-shapes/src/image-util/image-util.tsx
deleted file mode 100644
index a19667d54..000000000
--- a/libs/components/board-shapes/src/image-util/image-util.tsx
+++ /dev/null
@@ -1,226 +0,0 @@
-import { HTMLContainer, Utils } from '@tldraw/core';
-import {
- GHOSTED_OPACITY,
- ImageShape,
- TDImageAsset,
- TDMeta,
- TDShapeType,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-
-type T = ImageShape;
-type E = HTMLDivElement;
-
-export class ImageUtil extends TDShapeUtil {
- type = TDShapeType.Image as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override isAspectRatioLocked = true;
-
- override showCloneHandles = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'image',
- type: TDShapeType.Image,
- name: 'Image',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: { ...defaultStyle, isFilled: true },
- assetId: 'assetId',
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- asset = { src: '' },
- isBinding,
- isGhost,
- meta,
- events,
- onShapeChange,
- },
- ref
- ) => {
- const { size, style } = shape;
-
- const rImage = React.useRef(null);
- const rWrapper = React.useRef(null);
-
- React.useLayoutEffect(() => {
- const wrapper = rWrapper.current;
- if (!wrapper) return;
- const [width, height] = size;
- wrapper.style.width = `${width}px`;
- wrapper.style.height = `${height}px`;
- }, [size]);
-
- return (
-
- {isBinding && (
-
- )}
-
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const {
- size: [width, height],
- } = shape;
-
- return (
-
- );
- });
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override shouldRender = (prev: T, next: T) => {
- return next.size !== prev.size || next.style !== prev.style;
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-
- override getSvgElement = (shape: ImageShape) => {
- const bounds = this.getBounds(shape);
- const elm = document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'image'
- );
- elm.setAttribute('width', `${bounds.width}`);
- elm.setAttribute('height', `${bounds.height}`);
- elm.setAttribute('xmlns:xlink', `http://www.w3.org/1999/xlink`);
- return elm;
- };
-}
-
-const Wrapper = styled('div')<{
- isDarkMode: boolean;
- isFilled: boolean;
- isGhost: boolean;
-}>({
- pointerEvents: 'all',
- position: 'relative',
- fontSize: '2em',
- height: '100%',
- width: '100%',
- borderRadius: '3px',
- perspective: '800px',
- overflow: 'hidden',
- p: {
- userSelect: 'none',
- },
- img: {
- userSelect: 'none',
- },
- variants: {
- isGhost: {
- false: { opacity: 1 },
- true: { transition: 'opacity .2s', opacity: GHOSTED_OPACITY },
- },
- isFilled: {
- true: {},
- false: {},
- },
- isDarkMode: {
- true: {},
- false: {},
- },
- },
- compoundVariants: [
- {
- isFilled: true,
- isDarkMode: true,
- css: {
- boxShadow:
- '2px 3px 12px -2px rgba(0,0,0,.3), 1px 1px 4px rgba(0,0,0,.3), 1px 1px 2px rgba(0,0,0,.3)',
- },
- },
- {
- isFilled: true,
- isDarkMode: false,
- css: {
- boxShadow:
- '2px 3px 12px -2px rgba(0,0,0,.2), 1px 1px 4px rgba(0,0,0,.16), 1px 1px 2px rgba(0,0,0,.16)',
- },
- },
- ],
-});
-
-const ImageElement = styled('img')({
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- maxWidth: '100%',
- minWidth: '100%',
- pointerEvents: 'none',
- objectFit: 'cover',
- userSelect: 'none',
- borderRadius: 2,
-});
diff --git a/libs/components/board-shapes/src/image-util/index.ts b/libs/components/board-shapes/src/image-util/index.ts
deleted file mode 100644
index 512c90ac1..000000000
--- a/libs/components/board-shapes/src/image-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './image-util';
diff --git a/libs/components/board-shapes/src/index.ts b/libs/components/board-shapes/src/index.ts
deleted file mode 100644
index 3fc00bbbb..000000000
--- a/libs/components/board-shapes/src/index.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { TDShape, TDShapeType } from '@toeverything/components/board-types';
-import { ArrowUtil } from './arrow-util';
-import { DrawUtil } from './draw-util';
-import { EditorUtil } from './editor-util';
-import { EllipseUtil } from './ellipse-util';
-import { FrameUtil } from './frame-util';
-import { GroupUtil } from './group-util';
-import { HexagonUtil } from './hexagon-util';
-import { ImageUtil } from './image-util';
-import { PentagramUtil } from './pentagram-util';
-import { RectangleUtil } from './rectangle-util';
-import type { TDShapeUtil } from './TDShapeUtil';
-import { TriangleUtil } from './triangle-util';
-import { VideoUtil } from './video-util';
-import { WhiteArrowUtil } from './white-arrow-util';
-
-export { clearPrevSize } from './shared/get-text-size';
-export { defaultStyle } from './shared/shape-styles';
-export { TDShapeUtil } from './TDShapeUtil';
-export { getTrianglePoints } from './triangle-util/triangle-helpers';
-
-export const Rectangle = new RectangleUtil();
-export const Triangle = new TriangleUtil();
-export const Hexagon = new HexagonUtil();
-export const Pentagram = new PentagramUtil();
-export const WhiteArrow = new WhiteArrowUtil();
-export const Arrow = new ArrowUtil();
-export const Draw = new DrawUtil();
-export const Ellipse = new EllipseUtil();
-const Group = new GroupUtil();
-const Image = new ImageUtil();
-const Video = new VideoUtil();
-export const Frame = new FrameUtil();
-
-export const Editor = new EditorUtil();
-
-export const shapeUtils = {
- [TDShapeType.Rectangle]: Rectangle,
- [TDShapeType.Frame]: Frame,
- [TDShapeType.Triangle]: Triangle,
- [TDShapeType.Pentagram]: Pentagram,
- [TDShapeType.Hexagon]: Hexagon,
- [TDShapeType.WhiteArrow]: WhiteArrow,
- [TDShapeType.Arrow]: Arrow,
- [TDShapeType.Draw]: Draw,
- [TDShapeType.Ellipse]: Ellipse,
- [TDShapeType.Group]: Group,
- [TDShapeType.Image]: Image,
- [TDShapeType.Video]: Video,
- [TDShapeType.Editor]: Editor,
-};
-
-export const getShapeUtil = (
- shape: T | T['type']
-): TDShapeUtil | undefined => {
- if (typeof shape === 'string')
- return shapeUtils[shape] as unknown as TDShapeUtil;
- return shapeUtils[shape.type] as unknown as TDShapeUtil;
-};
diff --git a/libs/components/board-shapes/src/pentagram-util/PentagramUtil.tsx b/libs/components/board-shapes/src/pentagram-util/PentagramUtil.tsx
deleted file mode 100644
index 02770f4ee..000000000
--- a/libs/components/board-shapes/src/pentagram-util/PentagramUtil.tsx
+++ /dev/null
@@ -1,294 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectBoundsPolygon,
- intersectLineSegmentPolyline,
- intersectRayLineSegment,
-} from '@tldraw/intersect';
-import Vec from '@tldraw/vec';
-import {
- BINDING_DISTANCE,
- DashStyle,
- GHOSTED_OPACITY,
- LABEL_POINT,
- PentagramShape,
- TDMeta,
- TDShape,
- TDShapeType,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- getFontStyle,
- getShapeStyle,
- TextLabel,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { DashedPentagram } from './components/DashedPentagram';
-import { DrawPentagram } from './components/DrawPentagram';
-import { PentagramBindingIndicator } from './components/PentagramBindingIndicator';
-import { getPentagramCentroid, getPentagramPoints } from './pentagram-helpers';
-
-type T = PentagramShape;
-type E = HTMLDivElement;
-
-export class PentagramUtil extends TDShapeUtil {
- type = TDShapeType.Pentagram as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Pentagram,
- name: 'Triangle',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- bounds,
- isBinding,
- isEditing,
- isSelected,
- isGhost,
- meta,
- events,
- onShapeChange,
- onShapeBlur,
- },
- ref
- ) => {
- const {
- id,
- label = '',
- size,
- style,
- labelPoint = LABEL_POINT,
- } = shape;
- const font = getFontStyle(style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const Component =
- style.dash === DashStyle.Draw ? DrawPentagram : DashedPentagram;
- const handleLabelChange = React.useCallback(
- (label: string) => onShapeChange?.({ id, label }),
- [onShapeChange]
- );
- const offsetY = React.useMemo(() => {
- const center = Vec.div(size, 2);
- const centroid = getPentagramCentroid(size);
- return (centroid[1] - center[1]) * 0.72;
- }, [size]);
- return (
-
-
-
- {isBinding && }
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { size } = shape;
- return ;
- });
-
- private get_points(shape: T) {
- const {
- rotation = 0,
- point: [x, y],
- size: [w, h],
- } = shape;
- return [
- [x + w / 2, y],
- [x, y + h],
- [x + w, y + h],
- ].map(pt => Vec.rotWith(pt, this.getCenter(shape), rotation));
- }
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override getExpandedBounds = (shape: T) => {
- return Utils.getBoundsFromPoints(
- getPentagramPoints(shape.size, this.bindingDistance).map(pt =>
- Vec.add(pt, shape.point)
- )
- );
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- return intersectLineSegmentPolyline(A, B, this.get_points(shape))
- .didIntersect;
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds): boolean => {
- return (
- Utils.boundsContained(this.getBounds(shape), bounds) ||
- intersectBoundsPolygon(bounds, this.get_points(shape)).length > 0
- );
- };
-
- override getBindingPoint = (
- shape: T,
- fromShape: K,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- // Algorithm time! We need to find the binding point (a normalized point inside of the shape, or around the shape, where the arrow will point to) and the distance from the binding shape to the anchor.
-
- const expandedBounds = this.getExpandedBounds(shape);
-
- if (!Utils.pointInBounds(point, expandedBounds)) return;
-
- const points = getPentagramPoints(shape.size).map(pt =>
- Vec.add(pt, shape.point)
- );
-
- const expandedPoints = getPentagramPoints(
- shape.size,
- this.bindingDistance
- ).map(pt => Vec.add(pt, shape.point));
-
- const closestDistanceToEdge = Utils.pointsToLineSegments(points, true)
- .map(([a, b]) => Vec.distanceToLineSegment(a, b, point))
- .sort((a, b) => a - b)[0];
-
- if (
- !(
- Utils.pointInPolygon(point, expandedPoints) ||
- closestDistanceToEdge < this.bindingDistance
- )
- )
- return;
-
- const intersections = Utils.pointsToLineSegments(
- expandedPoints.concat([expandedPoints[0]])
- )
- .map(segment =>
- intersectRayLineSegment(
- origin,
- direction,
- segment[0],
- segment[1]
- )
- )
- .filter(intersection => intersection.didIntersect)
- .flatMap(intersection => intersection.points);
-
- if (!intersections.length) return;
-
- // The center of the triangle
- const center = Vec.add(getPentagramCentroid(shape.size), shape.point);
-
- // Find furthest intersection between ray from origin through point and expanded bounds. TODO: What if the shape has a curve? In that case, should we intersect the circle-from-three-points instead?
- const intersection = intersections.sort(
- (a, b) => Vec.dist(b, origin) - Vec.dist(a, origin)
- )[0];
-
- // The point between the handle and the intersection
- const middlePoint = Vec.med(point, intersection);
-
- let anchor: number[];
- let distance: number;
-
- if (bindAnywhere) {
- anchor =
- Vec.dist(point, center) < BINDING_DISTANCE / 2 ? center : point;
- distance = 0;
- } else {
- if (
- Vec.distanceToLineSegment(point, middlePoint, center) <
- BINDING_DISTANCE / 2
- ) {
- anchor = center;
- } else {
- anchor = middlePoint;
- }
-
- if (Utils.pointInPolygon(point, points)) {
- distance = this.bindingDistance;
- } else {
- distance = Math.max(
- this.bindingDistance,
- closestDistanceToEdge
- );
- }
- }
-
- const bindingPoint = Vec.divV(
- Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
-
- return {
- point: Vec.clampV(bindingPoint, 0, 1),
- distance,
- };
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/pentagram-util/components/DashedPentagram.tsx b/libs/components/board-shapes/src/pentagram-util/components/DashedPentagram.tsx
deleted file mode 100644
index 5fc715e0c..000000000
--- a/libs/components/board-shapes/src/pentagram-util/components/DashedPentagram.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import { getPentagramPoints } from '../pentagram-helpers';
-
-interface PentagramSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DashedPentagram = React.memo(function DashedPentagram({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: PentagramSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const sw = 1 + strokeWidth * 1.618;
- const points = getPentagramPoints(size);
- const sides = Utils.pointsToLineSegments(points, true);
- const paths = sides.map(([start, end], i) => {
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- Vec.dist(start, end),
- strokeWidth * 1.618,
- style.dash
- );
-
- return (
-
- );
- });
-
- const bgPath = points.join();
-
- return (
- <>
-
- {style.isFilled && (
-
- )}
- {paths}
- >
- );
-});
diff --git a/libs/components/board-shapes/src/pentagram-util/components/DrawPentagram.tsx b/libs/components/board-shapes/src/pentagram-util/components/DrawPentagram.tsx
deleted file mode 100644
index 17210e40a..000000000
--- a/libs/components/board-shapes/src/pentagram-util/components/DrawPentagram.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getPentagramIndicatorPathTDSnapshot,
- getPentagramPath,
-} from '../pentagram-helpers';
-
-interface PentagramSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DrawPentagram = React.memo(function DrawTriangle({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: PentagramSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const path_td_snapshot = getPentagramPath(id, size, style);
- const indicatorPath = getPentagramIndicatorPathTDSnapshot(id, size, style);
- return (
- <>
-
- {style.isFilled && (
-
- )}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/pentagram-util/components/PentagramBindingIndicator.tsx b/libs/components/board-shapes/src/pentagram-util/components/PentagramBindingIndicator.tsx
deleted file mode 100644
index 61be5887e..000000000
--- a/libs/components/board-shapes/src/pentagram-util/components/PentagramBindingIndicator.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-import { getPentagramPoints } from '../pentagram-helpers';
-
-interface TriangleBindingIndicatorProps {
- size: number[];
-}
-
-export function PentagramBindingIndicator({
- size,
-}: TriangleBindingIndicatorProps) {
- const trianglePoints = getPentagramPoints(size).join();
- return (
-
- );
-}
diff --git a/libs/components/board-shapes/src/pentagram-util/index.ts b/libs/components/board-shapes/src/pentagram-util/index.ts
deleted file mode 100644
index 8ecf54f7c..000000000
--- a/libs/components/board-shapes/src/pentagram-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './PentagramUtil';
diff --git a/libs/components/board-shapes/src/pentagram-util/pentagram-helpers.ts b/libs/components/board-shapes/src/pentagram-util/pentagram-helpers.ts
deleted file mode 100644
index 155109bb1..000000000
--- a/libs/components/board-shapes/src/pentagram-util/pentagram-helpers.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import getStroke, { getStrokePoints } from 'perfect-freehand';
-import { getOffsetPolygon, getShapeStyle } from '../shared';
-function getPonits(w: number, h: number) {
- return [
- [0, (76 / 200) * h],
- [(76 / 200) * w, (76 / 200) * h],
- [(100 / 200) * w, 0],
- [(124 / 200) * w, (76 / 200) * h],
- [(200 / 200) * w, (76 / 200) * h],
- [(138 / 200) * w, (124 / 200) * h],
- [(162 / 200) * w, (200 / 200) * h],
- [(100 / 200) * w, (153 / 200) * h],
- [(38 / 200) * w, (200 / 200) * h],
- [(62 / 200) * w, (124 / 200) * h],
- ];
-}
-
-export function getPentagramPoints(size: number[], offset = 0, rotation = 0) {
- const [w, h] = size;
- let points = getPonits(w, h);
- if (offset) points = getOffsetPolygon(points, offset);
- if (rotation)
- points = points.map(pt => Vec.rotWith(pt, [w / 2, h / 2], rotation));
-
- return points;
-}
-
-export function getPentagramCentroid(size: number[]) {
- const [w, h] = size;
- const points = getPonits(w, h);
- return [
- (points[0][0] + points[1][0] + points[2][0]) / 3,
- (points[0][1] + points[1][1] + points[2][1]) / 3,
- ];
-}
-
-function getPentagramDrawPoints(
- id: string,
- size: number[],
- strokeWidth: number
-) {
- const [w, h] = size;
- const getRandom = Utils.rng(id);
- // Random corner offsets
- const offsets = Array.from(Array(10)).map(() => {
- return [
- getRandom() * strokeWidth * 0.75,
- getRandom() * strokeWidth * 0.75,
- ];
- });
- // Corners
- const point = getPonits(w, h);
- const corners = [
- Vec.add(point[0], offsets[0]),
- Vec.add(point[1], offsets[1]),
- Vec.add(point[2], offsets[2]),
- Vec.add(point[3], offsets[3]),
- Vec.add(point[4], offsets[4]),
- Vec.add(point[5], offsets[5]),
- Vec.add(point[6], offsets[6]),
- Vec.add(point[7], offsets[7]),
- Vec.add(point[8], offsets[8]),
- Vec.add(point[9], offsets[9]),
- ];
-
- // Which side to start drawing first
- const rm = Math.round(Math.abs(getRandom() * 2 * 3));
- // Number of points per side
- // Inset each line by the corner radii and let the freehand algo
- // interpolate points for the corners.
- const lines = Utils.rotateArray(
- [
- Vec.pointsBetween(corners[0], corners[1], 32),
- Vec.pointsBetween(corners[1], corners[2], 32),
- Vec.pointsBetween(corners[2], corners[3], 32),
- Vec.pointsBetween(corners[3], corners[4], 32),
- Vec.pointsBetween(corners[4], corners[5], 32),
- Vec.pointsBetween(corners[5], corners[6], 32),
- Vec.pointsBetween(corners[6], corners[7], 32),
- Vec.pointsBetween(corners[7], corners[8], 32),
- Vec.pointsBetween(corners[8], corners[9], 32),
- Vec.pointsBetween(corners[9], corners[0], 32),
- ],
- rm
- );
- // For the final points, include the first half of the first line again,
- // so that the line wraps around and avoids ending on a sharp corner.
- // This has a bit of finesse and magic—if you change the points between
- // function, then you'll likely need to change this one too.
- const points = [...lines.flat(), ...lines[0]];
- return {
- points,
- };
-}
-
-function getDrawStrokeInfo(id: string, size: number[], style: ShapeStyles) {
- const { strokeWidth } = getShapeStyle(style);
- const { points } = getPentagramDrawPoints(id, size, strokeWidth);
- const options = {
- size: strokeWidth,
- thinning: 0.65,
- streamline: 0.3,
- smoothing: 1,
- simulatePressure: false,
- last: true,
- };
- return { points, options };
-}
-
-export function getPentagramPath(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const stroke = getStroke(points, options);
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getPentagramIndicatorPathTDSnapshot(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const strokePoints = getStrokePoints(points, options);
- return Utils.getSvgPathFromStroke(
- strokePoints.map(pt => pt.point.slice(0, 2)),
- false
- );
-}
diff --git a/libs/components/board-shapes/src/rectangle-util/RectangleUtil.tsx b/libs/components/board-shapes/src/rectangle-util/RectangleUtil.tsx
deleted file mode 100644
index c5fbbaa4d..000000000
--- a/libs/components/board-shapes/src/rectangle-util/RectangleUtil.tsx
+++ /dev/null
@@ -1,168 +0,0 @@
-import { SVGContainer, Utils } from '@tldraw/core';
-import {
- DashStyle,
- GHOSTED_OPACITY,
- LABEL_POINT,
- RectangleShape,
- TDMeta,
- TDShapeType,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- getFontStyle,
- getShapeStyle,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TextLabel } from '../shared/text-label';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { BindingIndicator } from './components/BindingIndicator';
-import { DashedRectangle } from './components/DashedRectangle';
-import { DrawRectangle } from './components/DrawRectangle';
-import { getRectangleIndicatorPathTDSnapshot } from './rectangle-helpers';
-
-type T = RectangleShape;
-type E = HTMLDivElement;
-
-export class RectangleUtil extends TDShapeUtil {
- type = TDShapeType.Rectangle as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Rectangle,
- name: 'Rectangle',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- isEditing,
- isBinding,
- isSelected,
- isGhost,
- meta,
- bounds,
- events,
- onShapeBlur,
- onShapeChange,
- },
- ref
- ) => {
- const {
- id,
- size,
- style,
- label = '',
- labelPoint = LABEL_POINT,
- } = shape;
- const font = getFontStyle(style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const Component =
- style.dash === DashStyle.Draw ? DrawRectangle : DashedRectangle;
- const handleLabelChange = React.useCallback(
- (label: string) => onShapeChange?.({ id, label }),
- [onShapeChange]
- );
- return (
-
-
-
- {isBinding && (
-
- )}
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { id, style, size } = shape;
-
- const styles = getShapeStyle(style, false);
- const sw = styles.strokeWidth;
-
- if (style.dash === DashStyle.Draw) {
- return (
-
- );
- }
-
- return (
-
- );
- });
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/rectangle-util/components/BindingIndicator.tsx b/libs/components/board-shapes/src/rectangle-util/components/BindingIndicator.tsx
deleted file mode 100644
index ee349f163..000000000
--- a/libs/components/board-shapes/src/rectangle-util/components/BindingIndicator.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-
-interface BindingIndicatorProps {
- strokeWidth: number;
- size: number[];
-}
-export function BindingIndicator({ strokeWidth, size }: BindingIndicatorProps) {
- return (
-
- );
-}
diff --git a/libs/components/board-shapes/src/rectangle-util/components/DashedRectangle.tsx b/libs/components/board-shapes/src/rectangle-util/components/DashedRectangle.tsx
deleted file mode 100644
index 36d4475b9..000000000
--- a/libs/components/board-shapes/src/rectangle-util/components/DashedRectangle.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { Utils } from '@tldraw/core';
-import {
- BINDING_DISTANCE,
- ShapeStyles,
-} from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-
-interface RectangleSvgProps {
- id: string;
- style: ShapeStyles;
- isSelected: boolean;
- size: number[];
- isDarkMode: boolean;
-}
-
-export const DashedRectangle = React.memo(function DashedRectangle({
- id,
- style,
- size,
- isSelected,
- isDarkMode,
-}: RectangleSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
-
- const sw = 1 + strokeWidth * 1.618;
-
- const w = Math.max(0, size[0] - sw / 2);
- const h = Math.max(0, size[1] - sw / 2);
-
- const strokes: [number[], number[], number][] = [
- [[sw / 2, sw / 2], [w, sw / 2], w - sw / 2],
- [[w, sw / 2], [w, h], h - sw / 2],
- [[w, h], [sw / 2, h], w - sw / 2],
- [[sw / 2, h], [sw / 2, sw / 2], h - sw / 2],
- ];
-
- const paths = strokes.map(([start, end, length], i) => {
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- length,
- strokeWidth * 1.618,
- style.dash
- );
-
- return (
-
- );
- });
-
- return (
- <>
-
- {style.isFilled && (
-
- )}
-
- {paths}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/rectangle-util/components/DrawRectangle.tsx b/libs/components/board-shapes/src/rectangle-util/components/DrawRectangle.tsx
deleted file mode 100644
index 9858ac698..000000000
--- a/libs/components/board-shapes/src/rectangle-util/components/DrawRectangle.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getRectangleIndicatorPathTDSnapshot,
- getRectanglePath,
-} from '../rectangle-helpers';
-
-interface RectangleSvgProps {
- id: string;
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
- size: number[];
-}
-
-export const DrawRectangle = React.memo(function DrawRectangle({
- id,
- style,
- size,
- isSelected,
- isDarkMode,
-}: RectangleSvgProps) {
- const { isFilled } = style;
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const path_td_snapshot = getRectanglePath(id, style, size);
- const innerPath = getRectangleIndicatorPathTDSnapshot(id, style, size);
-
- return (
- <>
-
- {isFilled && (
-
- )}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/rectangle-util/index.ts b/libs/components/board-shapes/src/rectangle-util/index.ts
deleted file mode 100644
index 8026a008a..000000000
--- a/libs/components/board-shapes/src/rectangle-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './RectangleUtil';
diff --git a/libs/components/board-shapes/src/rectangle-util/rectangle-helpers.ts b/libs/components/board-shapes/src/rectangle-util/rectangle-helpers.ts
deleted file mode 100644
index 4b10de4e3..000000000
--- a/libs/components/board-shapes/src/rectangle-util/rectangle-helpers.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import getStroke, { getStrokePoints } from 'perfect-freehand';
-import { getShapeStyle } from '../shared';
-
-function getRectangleDrawPoints(
- id: string,
- style: ShapeStyles,
- size: number[]
-) {
- const styles = getShapeStyle(style);
-
- const getRandom = Utils.rng(id);
-
- const sw = styles.strokeWidth;
-
- // Dimensions
- const w = Math.max(0, size[0]);
- const h = Math.max(0, size[1]);
-
- // Random corner offsets
- const offsets = Array.from(Array(4)).map(() => {
- return [getRandom() * sw * 0.75, getRandom() * sw * 0.75];
- });
-
- // Corners
- const tl = Vec.add([sw / 2, sw / 2], offsets[0]);
- const tr = Vec.add([w - sw / 2, sw / 2], offsets[1]);
- const br = Vec.add([w - sw / 2, h - sw / 2], offsets[2]);
- const bl = Vec.add([sw / 2, h - sw / 2], offsets[3]);
-
- // Which side to start drawing first
- const rm = Math.round(Math.abs(getRandom() * 2 * 4));
-
- // Corner radii
- const rx = Math.min(w / 4, sw * 2);
- const ry = Math.min(h / 4, sw * 2);
-
- // Number of points per side
- const px = Math.max(8, Math.floor(w / 16));
- const py = Math.max(8, Math.floor(h / 16));
-
- // Inset each line by the corner radii and let the freehand algo
- // interpolate points for the corners.
- const lines = Utils.rotateArray(
- [
- Vec.pointsBetween(Vec.add(tl, [rx, 0]), Vec.sub(tr, [rx, 0]), px),
- Vec.pointsBetween(Vec.add(tr, [0, ry]), Vec.sub(br, [0, ry]), py),
- Vec.pointsBetween(Vec.sub(br, [rx, 0]), Vec.add(bl, [rx, 0]), px),
- Vec.pointsBetween(Vec.sub(bl, [0, ry]), Vec.add(tl, [0, ry]), py),
- ],
- rm
- );
-
- // For the final points, include the first half of the first line again,
- // so that the line wraps around and avoids ending on a sharp corner.
- // This has a bit of finesse and magic—if you change the points between
- // function, then you'll likely need to change this one too.
-
- const points = [...lines.flat(), ...lines[0]].slice(
- 5,
- Math.floor((rm % 2 === 0 ? px : py) / -2) + 3
- );
-
- return {
- points,
- };
-}
-
-function getDrawStrokeInfo(id: string, style: ShapeStyles, size: number[]) {
- const { points } = getRectangleDrawPoints(id, style, size);
- const { strokeWidth } = getShapeStyle(style);
- const options = {
- size: strokeWidth,
- thinning: 0.65,
- streamline: 0.3,
- smoothing: 1,
- simulatePressure: false,
- last: true,
- };
- return { points, options };
-}
-
-export function getRectanglePath(
- id: string,
- style: ShapeStyles,
- size: number[]
-) {
- const { points, options } = getDrawStrokeInfo(id, style, size);
- const stroke = getStroke(points, options);
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getRectangleIndicatorPathTDSnapshot(
- id: string,
- style: ShapeStyles,
- size: number[]
-) {
- const { points, options } = getDrawStrokeInfo(id, style, size);
- const strokePoints = getStrokePoints(points, options);
- return Utils.getSvgPathFromStroke(
- strokePoints.map(pt => pt.point.slice(0, 2)),
- false
- );
-}
diff --git a/libs/components/board-shapes/src/shared/get-bounds-rectangle.ts b/libs/components/board-shapes/src/shared/get-bounds-rectangle.ts
deleted file mode 100644
index f216a9b27..000000000
--- a/libs/components/board-shapes/src/shared/get-bounds-rectangle.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { TLBounds, TLShape, Utils } from '@tldraw/core';
-
-/**
- * Find the bounds of a rectangular shape.
- * @param shape
- * @param boundsCache
- */
-export function getBoundsRectangle(
- shape: T,
- boundsCache: WeakMap
-) {
- const bounds = Utils.getFromCache(boundsCache, shape, () => {
- const [width, height] = shape.size;
- return {
- minX: 0,
- maxX: width,
- minY: 0,
- maxY: height,
- width,
- height,
- };
- });
-
- return Utils.translateBounds(bounds, shape.point);
-}
diff --git a/libs/components/board-shapes/src/shared/get-text-align.ts b/libs/components/board-shapes/src/shared/get-text-align.ts
deleted file mode 100644
index 83f75be7f..000000000
--- a/libs/components/board-shapes/src/shared/get-text-align.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { AlignStyle } from '@toeverything/components/board-types';
-
-const ALIGN_VALUES = {
- [AlignStyle.Start]: 'left',
- [AlignStyle.Middle]: 'center',
- [AlignStyle.End]: 'right',
- [AlignStyle.Justify]: 'justify',
-} as const;
-
-export function getTextAlign(alignStyle: AlignStyle = AlignStyle.Start) {
- return ALIGN_VALUES[alignStyle];
-}
diff --git a/libs/components/board-shapes/src/shared/get-text-size.ts b/libs/components/board-shapes/src/shared/get-text-size.ts
deleted file mode 100644
index cee76faa2..000000000
--- a/libs/components/board-shapes/src/shared/get-text-size.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { LETTER_SPACING } from '@toeverything/components/board-types';
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-let melm: any;
-
-function getMeasurementDiv() {
- // A div used for measurement
- document.getElementById('__textLabelMeasure')?.remove();
-
- const pre = document.createElement('pre');
- pre.id = '__textLabelMeasure';
-
- Object.assign(pre.style, {
- whiteSpace: 'pre',
- width: 'auto',
- border: '1px solid transparent',
- padding: '4px',
- margin: '0px',
- letterSpacing: LETTER_SPACING,
- opacity: '0',
- position: 'absolute',
- top: '-500px',
- left: '0px',
- zIndex: '9999',
- pointerEvents: 'none',
- userSelect: 'none',
- alignmentBaseline: 'mathematical',
- dominantBaseline: 'mathematical',
- });
-
- pre.tabIndex = -1;
-
- document.body.appendChild(pre);
- return pre;
-}
-
-if (typeof window !== 'undefined') {
- melm = getMeasurementDiv();
-}
-
-let prevText = '';
-let prevFont = '';
-let prevSize = [0, 0];
-
-export function clearPrevSize() {
- prevText = '';
-}
-
-export function getTextLabelSize(text: string, font: string) {
- if (!text) {
- return [16, 32];
- }
-
- if (!melm) {
- // We're in SSR
- return [10, 10];
- }
-
- if (!melm.parent) document.body.appendChild(melm);
-
- if (text === prevText && font === prevFont) {
- return prevSize;
- }
-
- prevText = text;
- prevFont = font;
-
- melm.textContent = text;
- melm.style.font = font;
-
- // In tests, offsetWidth and offsetHeight will be 0
- const width = melm.offsetWidth || 1;
- const height = melm.offsetHeight || 1;
-
- prevSize = [width, height];
- return prevSize;
-}
diff --git a/libs/components/board-shapes/src/shared/get-text-svg-element.ts b/libs/components/board-shapes/src/shared/get-text-svg-element.ts
deleted file mode 100644
index 3f5a496ed..000000000
--- a/libs/components/board-shapes/src/shared/get-text-svg-element.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import type { TLBounds } from '@tldraw/core';
-import {
- AlignStyle,
- LINE_HEIGHT,
- ShapeStyles,
-} from '@toeverything/components/board-types';
-import { getTextAlign } from './get-text-align';
-import { getFontFace, getFontSize } from './shape-styles';
-
-export function getTextSvgElement(
- text: string,
- style: ShapeStyles,
- bounds: TLBounds
-) {
- const fontSize = getFontSize(style.fontSize, style.font);
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
- const scale = style.scale ?? 1;
-
- const textLines = text.split('\n').map((line, i) => {
- const textElm = document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'text'
- );
- textElm.textContent = line;
- textElm.setAttribute('y', fontSize * (0.5 + i * LINE_HEIGHT) + '');
- textElm.setAttribute('letter-spacing', fontSize * -0.03 + '');
- textElm.setAttribute('font-size', fontSize + 'px');
- textElm.setAttribute(
- 'font-family',
- getFontFace(style.font).slice(1, -1)
- );
- textElm.setAttribute('text-align', getTextAlign(style.textAlign));
- textElm.setAttribute('text-align', getTextAlign(style.textAlign));
- textElm.setAttribute('alignment-baseline', 'central');
- if (style.scale !== 1) {
- textElm.setAttribute('transform', `scale(${style.scale})`);
- }
- g.appendChild(textElm);
-
- return textElm;
- });
-
- switch (style.textAlign) {
- case AlignStyle.Middle: {
- g.setAttribute('text-align', 'center');
- g.setAttribute('text-anchor', 'middle');
- textLines.forEach(textElm => {
- textElm.setAttribute('x', bounds.width / 2 / scale + '');
- });
- break;
- }
- case AlignStyle.End: {
- g.setAttribute('text-align', 'right');
- g.setAttribute('text-anchor', 'end');
- textLines.forEach(textElm =>
- textElm.setAttribute('x', bounds.width / scale + '')
- );
- break;
- }
- case AlignStyle.Start: {
- g.setAttribute('text-align', 'left');
- g.setAttribute('text-anchor', 'start');
- }
- }
-
- return g;
-}
diff --git a/libs/components/board-shapes/src/shared/index.ts b/libs/components/board-shapes/src/shared/index.ts
deleted file mode 100644
index 95fe5ace9..000000000
--- a/libs/components/board-shapes/src/shared/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export * from './get-bounds-rectangle';
-export * from './get-text-align';
-export * from './get-text-size';
-export * from './get-text-svg-element';
-export * from './label-mask';
-export * from './normalize-text';
-export * from './polygon-utils';
-export * from './shape-styles';
-export * from './stop-propagation';
-export * from './text-label';
-export * from './transform-rectangle';
-export * from './transform-single-rectangle';
diff --git a/libs/components/board-shapes/src/shared/label-mask.tsx b/libs/components/board-shapes/src/shared/label-mask.tsx
deleted file mode 100644
index d7b62eda1..000000000
--- a/libs/components/board-shapes/src/shared/label-mask.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { TLBounds } from '@tldraw/core';
-
-interface WithLabelMaskProps {
- id: string;
- bounds: TLBounds;
- labelSize: number[];
- offset?: number[];
- scale?: number;
-}
-
-export function LabelMask({
- id,
- bounds,
- labelSize,
- offset,
- scale = 1,
-}: WithLabelMaskProps) {
- return (
-
-
-
-
-
-
- );
-}
diff --git a/libs/components/board-shapes/src/shared/normalize-text.ts b/libs/components/board-shapes/src/shared/normalize-text.ts
deleted file mode 100644
index 1b4f30a26..000000000
--- a/libs/components/board-shapes/src/shared/normalize-text.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-const fixNewLines = /\r?\n|\r/g;
-
-export function normalizeText(text: string) {
- return text
- .replace(fixNewLines, '\n')
- .split('\n')
- .map(x => x || ' ')
- .join('\n');
-}
diff --git a/libs/components/board-shapes/src/shared/polygon-utils.ts b/libs/components/board-shapes/src/shared/polygon-utils.ts
deleted file mode 100644
index ff1640411..000000000
--- a/libs/components/board-shapes/src/shared/polygon-utils.ts
+++ /dev/null
@@ -1,198 +0,0 @@
-import { intersectLineLine } from '@tldraw/intersect';
-import Vec from '@tldraw/vec';
-
-const PI2 = Math.PI * 2;
-
-type Vert = number[];
-type Edge = Vert[];
-type Polygon = Vert[];
-
-export class PolygonUtils {
- static inward_edge_normal(edge: Edge) {
- // Assuming that polygon vertices are in clockwise order
- const delta = Vec.sub(edge[1], edge[0]);
- const len = Vec.len2(delta);
- return [-delta[0] / len, delta[1] / len];
- }
-
- static outward_edge_normal(edge: Edge) {
- return Vec.neg(PolygonUtils.inward_edge_normal(edge));
- }
-
- // If the slope of line v1,v2 greater than the slope of v1,p then p is on the left side of v1,v2 and the return value is > 0.
- // If p is colinear with v1,v2 then return 0, otherwise return a value < 0.
-
- static left_side = Vec.isLeft;
-
- static is_reflex_vertex(polygon: Polygon, index: number) {
- const len = polygon.length;
- // Assuming that polygon vertices are in clockwise order
- const v0 = polygon[(index + len - 1) % len];
- const v1 = polygon[index];
- const v2 = polygon[(index + 1) % len];
- if (PolygonUtils.left_side(v0, v2, v1) < 0) return true;
- return false;
- }
-
- static get_edges(vertices: Vert[]) {
- return vertices.map((vert, i) => [
- vert,
- vertices[(i + 1) % vertices.length],
- ]);
- }
-
- // based on http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/, A => "line a", B => "line b"
- static edges_intersection([A1, A2]: number[][], [B1, B2]: number[][]) {
- const den =
- (B2[1] - B1[1]) * (A2[0] - A1[0]) -
- (B2[0] - B1[0]) * (A2[1] - A1[1]);
-
- if (den == 0) return null; // lines are parallel or conincident
-
- const ua =
- ((B2[0] - B1[0]) * (A1[1] - B1[1]) -
- (B2[1] - B1[1]) * (A1[0] - B1[0])) /
- den;
-
- const ub =
- ((A2[0] - A1[0]) * (A1[1] - B1[1]) -
- (A2[1] - A1[1]) * (A1[0] - B1[0])) /
- den;
-
- if (ua < 0 || ub < 0 || ua > 1 || ub > 1) return null;
-
- return [A1[0] + ua * (A2[0] - A1[0]), A1[1] + ua * (A2[1] - A1[1])];
- }
-
- static append_arc(
- polygon: number[][],
- center: number[],
- radius: number,
- startVertex: number[],
- endVertex: number[],
- isPaddingBoundary = false
- ) {
- const vertices = [...polygon];
- let startAngle = Math.atan2(
- startVertex[1] - center[1],
- startVertex[0] - center[0]
- );
- let endAngle = Math.atan2(
- endVertex[1] - center[1],
- endVertex[0] - center[0]
- );
- if (startAngle < 0) startAngle += PI2;
- if (endAngle < 0) endAngle += PI2;
- const arcSegmentCount = 5; // An odd number so that one arc vertex will be eactly arcRadius from center.
- const angle =
- startAngle > endAngle
- ? startAngle - endAngle
- : startAngle + PI2 - endAngle;
- const angle5 =
- (isPaddingBoundary ? -angle : PI2 - angle) / arcSegmentCount;
-
- vertices.push(startVertex);
- for (let i = 1; i < arcSegmentCount; ++i) {
- const angle = startAngle + angle5 * i;
- vertices.push([
- center[0] + Math.cos(angle) * radius,
- center[1] + Math.sin(angle) * radius,
- ]);
- }
- vertices.push(endVertex);
-
- return vertices;
- }
-
- static create_offset_edge(edge: Edge, offset: number[]) {
- return edge.map(vert => Vec.add(vert, offset));
- }
-
- static get_offset_polygon(polygon: Polygon, offset = 0) {
- const edges = PolygonUtils.get_edges(polygon);
-
- const offsetEdges = edges.map(edge =>
- PolygonUtils.create_offset_edge(
- edge,
- Vec.mul(PolygonUtils.outward_edge_normal(edge), offset)
- )
- );
-
- const vertices = [];
-
- for (let i = 0; i < offsetEdges.length; i++) {
- const thisEdge = offsetEdges[i];
- const prevEdge =
- offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];
- const vertex = PolygonUtils.edges_intersection(prevEdge, thisEdge);
- if (vertex) vertices.push(vertex);
- else {
- PolygonUtils.append_arc(
- vertices,
- edges[i][0],
- offset,
- prevEdge[1],
- thisEdge[0],
- false
- );
- }
- }
-
- // var marginPolygon = PolygonUtils.createPolygon(vertices)
- // marginPolygon.offsetEdges = offsetEdges
- return vertices;
- }
-
- static create_padding_polygon(polygon: number[][][], shapePadding = 0) {
- const offsetEdges = polygon.map(edge =>
- PolygonUtils.create_offset_edge(
- edge,
- PolygonUtils.inward_edge_normal(edge)
- )
- );
-
- const vertices = [];
- for (let i = 0; i < offsetEdges.length; i++) {
- const thisEdge = offsetEdges[i];
- const prevEdge =
- offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length];
- const vertex = PolygonUtils.edges_intersection(prevEdge, thisEdge);
- if (vertex) vertices.push(vertex);
- else {
- PolygonUtils.append_arc(
- vertices,
- polygon[i][0],
- shapePadding,
- prevEdge[1],
- thisEdge[0],
- true
- );
- }
- }
-
- return vertices;
- }
-}
-
-export function getOffsetPolygon(points: number[][], offset: number) {
- if (points.length < 3) throw Error('Polygon must have at least 3 points');
- const len = points.length;
- return points
- .map((point, i) => [point, points[(i + 1) % len]])
- .map(([A, B]) => {
- const offsetVector = Vec.mul(
- Vec.per(Vec.uni(Vec.sub(B, A))),
- offset
- );
- return [Vec.add(A, offsetVector), Vec.add(B, offsetVector)];
- })
- .map((edge, i, edges) => {
- const intersection = intersectLineLine(
- edge,
- edges[(i + 1) % edges.length]
- );
- if (intersection === undefined)
- throw Error('Expected an intersection');
- return intersection;
- });
-}
diff --git a/libs/components/board-shapes/src/shared/shape-styles.ts b/libs/components/board-shapes/src/shared/shape-styles.ts
deleted file mode 100644
index b21ab6152..000000000
--- a/libs/components/board-shapes/src/shared/shape-styles.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-import { Utils } from '@tldraw/core';
-import {
- AlignStyle,
- ColorStyle,
- DashStyle,
- FontSizeStyle,
- FontStyle,
- ShapeStyles,
- StrokeWidth,
- Theme,
-} from '@toeverything/components/board-types';
-
-const canvasLight = '#fafafa';
-
-const canvasDark = '#343d45';
-
-export const commonColors = {
- black: '#3A4C5C',
- white: '#FFFFFF',
-};
-
-export const strokes: Record> = {
- light: {
- ...commonColors,
- white: '#1d1d1d',
- },
- dark: {
- ...(Object.fromEntries(
- Object.entries(commonColors).map(([k, v]) => [
- k,
- Utils.lerpColor(v, canvasDark, 0.1),
- ])
- ) as Record),
- white: '#cecece',
- black: '#cecece',
- },
-};
-
-export const fills: Record> = {
- light: {
- ...(Object.fromEntries(
- Object.entries(commonColors).map(([k, v]) => [
- k,
- Utils.lerpColor(v, canvasLight, 0.82),
- ])
- ) as Record),
- white: '#fefefe',
- },
- dark: {
- ...(Object.fromEntries(
- Object.entries(commonColors).map(([k, v]) => [
- k,
- Utils.lerpColor(v, canvasDark, 0.82),
- ])
- ) as Record),
- white: 'rgb(30,33,37)',
- black: '#1e1e1f',
- },
-};
-
-const fontFaces = {
- [FontStyle.Script]: '"Caveat Brush"',
- [FontStyle.Sans]: '"Source Sans Pro"',
- [FontStyle.Serif]: '"Crimson Pro"',
- [FontStyle.Mono]: '"Source Code Pro"',
-};
-
-const fontSizeModifiers = {
- [FontStyle.Script]: 1,
- [FontStyle.Sans]: 1,
- [FontStyle.Serif]: 1,
- [FontStyle.Mono]: 1,
-};
-
-const _lineHeights = {
- [FontSizeStyle.h1]: 40,
- [FontSizeStyle.h2]: 34,
- [FontSizeStyle.h3]: 28,
- [FontSizeStyle.body]: 22,
-};
-
-export function getFontSize(
- size: FontSizeStyle,
- fontStyle: FontStyle = FontStyle.Script
-): number {
- return size * fontSizeModifiers[fontStyle];
-}
-
-function getLineHeight(size: FontSizeStyle): number {
- return _lineHeights[size];
-}
-
-export function getFontFace(font: FontStyle = FontStyle.Script): string {
- return fontFaces[font];
-}
-
-export function getStickyFontSize(size: FontSizeStyle): number {
- return size;
-}
-
-export function getFontStyle(style: ShapeStyles): string {
- const fontSize = getFontSize(style.fontSize, style.font);
- const fontFace = getFontFace(style.font);
- const lineHeight = getLineHeight(style.fontSize);
- const { scale = 1 } = style;
-
- return `${fontSize * scale}px/${lineHeight}px ${fontFace}`;
-}
-
-export function getStickyFontStyle(style: ShapeStyles): string {
- const fontSize = getStickyFontSize(style.fontSize);
- const fontFace = getFontFace(style.font);
- const { scale = 1 } = style;
-
- return `${fontSize * scale}px/1 ${fontFace}`;
-}
-
-export function getShapeStyle(
- style: ShapeStyles,
- isDarkMode?: boolean
-): {
- stroke: string;
- fill: string;
- strokeWidth: number;
-} {
- const { stroke, strokeWidth, fill, isFilled, dash } = style;
-
- return {
- stroke: dash === DashStyle.None ? 'none' : stroke,
- fill: isFilled ? fill : 'none',
- strokeWidth,
- };
-}
-
-export const defaultStyle: ShapeStyles = {
- stroke: commonColors.black,
- strokeWidth: StrokeWidth.s1,
- fill: 'none',
- fontSize: FontSizeStyle.body,
- isFilled: false,
- dash: DashStyle.Draw,
- scale: 1,
-};
-
-export const defaultTextStyle: ShapeStyles = {
- ...defaultStyle,
- font: FontStyle.Script,
- textAlign: AlignStyle.Middle,
-};
diff --git a/libs/components/board-shapes/src/shared/stop-propagation.ts b/libs/components/board-shapes/src/shared/stop-propagation.ts
deleted file mode 100644
index a2ed6e19f..000000000
--- a/libs/components/board-shapes/src/shared/stop-propagation.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import type React from 'react';
-
-export const stopPropagation = (
- e: KeyboardEvent | React.SyntheticEvent
-) => e.stopPropagation();
diff --git a/libs/components/board-shapes/src/shared/text-area-utils.ts b/libs/components/board-shapes/src/shared/text-area-utils.ts
deleted file mode 100644
index d5050fb57..000000000
--- a/libs/components/board-shapes/src/shared/text-area-utils.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-// Adapted (mostly copied) the work of https://github.com/fregante
-// Copyright (c) Federico Brigante (bfred.it)
-
-type ReplacerCallback = (substring: string, ...args: unknown[]) => string;
-
-const INDENT = ' ';
-
-export class TextAreaUtils {
- static insert_text_firefox(
- field: HTMLTextAreaElement | HTMLInputElement,
- text: string
- ): void {
- // Found on https://www.everythingfrontend.com/posts/insert-text-into-textarea-at-cursor-position.html 🎈
- field.setRangeText(
- text,
- field.selectionStart || 0,
- field.selectionEnd || 0,
- 'end' // Without this, the cursor is either at the beginning or `text` remains selected
- );
-
- field.dispatchEvent(
- new InputEvent('input', {
- data: text,
- inputType: 'insertText',
- isComposing: false, // TODO: fix @types/jsdom, this shouldn't be required
- })
- );
- }
-
- /** Inserts `text` at the cursor’s position, replacing any selection, with **undo** support and by firing the `input` event. */
- static insert(
- field: HTMLTextAreaElement | HTMLInputElement,
- text: string
- ): void {
- const document = field.ownerDocument;
- const initialFocus = document.activeElement;
- if (initialFocus !== field) {
- field.focus();
- }
-
- if (!document.execCommand('insertText', false, text)) {
- TextAreaUtils.insert_text_firefox(field, text);
- }
-
- if (initialFocus === document.body) {
- field.blur();
- } else if (
- initialFocus instanceof HTMLElement &&
- initialFocus !== field
- ) {
- initialFocus.focus();
- }
- }
-
- /** Replaces the entire content, equivalent to `field.value = text` but with **undo** support and by firing the `input` event. */
- static set(
- field: HTMLTextAreaElement | HTMLInputElement,
- text: string
- ): void {
- field.select();
- TextAreaUtils.insert(field, text);
- }
-
- /** Get the selected text in a field or an empty string if nothing is selected. */
- static get_selection(
- field: HTMLTextAreaElement | HTMLInputElement
- ): string {
- const { selectionStart, selectionEnd } = field;
- return field.value.slice(
- selectionStart ? selectionStart : undefined,
- selectionEnd ? selectionEnd : undefined
- );
- }
-
- /** Adds the `wrappingText` before and after field’s selection (or cursor). If `endWrappingText` is provided, it will be used instead of `wrappingText` at on the right. */
- static wrap_selection(
- field: HTMLTextAreaElement | HTMLInputElement,
- wrap: string,
- wrapEnd?: string
- ): void {
- const { selectionStart, selectionEnd } = field;
- const selection = TextAreaUtils.get_selection(field);
- TextAreaUtils.insert(field, wrap + selection + (wrapEnd ?? wrap));
-
- // Restore the selection around the previously-selected text
- field.selectionStart = (selectionStart || 0) + wrap.length;
- field.selectionEnd = (selectionEnd || 0) + wrap.length;
- }
-
- /** Finds and replaces strings and regex in the field’s value, like `field.value = field.value.replace()` but better */
- static replace(
- field: HTMLTextAreaElement | HTMLInputElement,
- searchValue: string | RegExp,
- replacer: string | ReplacerCallback
- ): void {
- /** Remembers how much each match offset should be adjusted */
- let drift = 0;
-
- field.value.replace(searchValue, (...args): string => {
- // Select current match to replace it later
- const matchStart = drift + (args[args.length - 2] as number);
- const matchLength = args[0].length;
- field.selectionStart = matchStart;
- field.selectionEnd = matchStart + matchLength;
-
- const replacement =
- typeof replacer === 'string' ? replacer : replacer(...args);
- TextAreaUtils.insert(field, replacement);
-
- // Select replacement. Without this, the cursor would be after the replacement
- field.selectionStart = matchStart;
- drift += replacement.length - matchLength;
- return replacement;
- });
- }
-
- static find_line_end(value: string, currentEnd: number): number {
- // Go to the beginning of the last line
- const lastLineStart = value.lastIndexOf('\n', currentEnd - 1) + 1;
-
- // There's nothing to unindent after the last cursor, so leave it as is
- if (value.charAt(lastLineStart) !== '\t') {
- return currentEnd;
- }
-
- return lastLineStart + 1; // Include the first character, which will be a tab
- }
-
- static indent(element: HTMLTextAreaElement): void {
- const { selectionStart, selectionEnd, value } = element;
- const selectedContrast = value.slice(selectionStart, selectionEnd);
- // The first line should be indented, even if it starts with `\n`
- // The last line should only be indented if includes any character after `\n`
- const lineBreakCount = /\n/g.exec(selectedContrast)?.length;
-
- if (lineBreakCount && lineBreakCount > 0) {
- // Select full first line to replace everything at once
- const firstLineStart =
- value.lastIndexOf('\n', selectionStart - 1) + 1;
-
- const newSelection = element.value.slice(
- firstLineStart,
- selectionEnd - 1
- );
- const indentedText = newSelection.replace(
- /^|\n/g, // Match all line starts
- `$&${INDENT}`
- );
- const replacementsCount = indentedText.length - newSelection.length;
-
- // Replace newSelection with indentedText
- element.setSelectionRange(firstLineStart, selectionEnd - 1);
- TextAreaUtils.insert(element, indentedText);
-
- // Restore selection position, including the indentation
- element.setSelectionRange(
- selectionStart + 1,
- selectionEnd + replacementsCount
- );
- } else {
- TextAreaUtils.insert(element, INDENT);
- }
- }
-
- // The first line should always be unindented
- // The last line should only be unindented if the selection includes any characters after `\n`
- static unindent(element: HTMLTextAreaElement): void {
- const { selectionStart, selectionEnd, value } = element;
-
- // Select the whole first line because it might contain \t
- const firstLineStart = value.lastIndexOf('\n', selectionStart - 1) + 1;
- const minimumSelectionEnd = TextAreaUtils.find_line_end(
- value,
- selectionEnd
- );
-
- const newSelection = element.value.slice(
- firstLineStart,
- minimumSelectionEnd
- );
- const indentedText = newSelection.replace(/(^|\n)(\t| {1,2})/g, '$1');
- const replacementsCount = newSelection.length - indentedText.length;
-
- // Replace newSelection with indentedText
- element.setSelectionRange(firstLineStart, minimumSelectionEnd);
- TextAreaUtils.insert(element, indentedText);
-
- // Restore selection position, including the indentation
- const firstLineIndentation = /\t| {1,2}/.exec(
- value.slice(firstLineStart, selectionStart)
- );
-
- const difference = firstLineIndentation
- ? firstLineIndentation[0].length
- : 0;
-
- const newSelectionStart = selectionStart - difference;
- element.setSelectionRange(
- selectionStart - difference,
- Math.max(newSelectionStart, selectionEnd - replacementsCount)
- );
- }
-}
diff --git a/libs/components/board-shapes/src/shared/text-label.tsx b/libs/components/board-shapes/src/shared/text-label.tsx
deleted file mode 100644
index ba40f38f0..000000000
--- a/libs/components/board-shapes/src/shared/text-label.tsx
+++ /dev/null
@@ -1,281 +0,0 @@
-import {
- GHOSTED_OPACITY,
- LETTER_SPACING,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import { getTextLabelSize } from './get-text-size';
-import { normalizeText } from './normalize-text';
-import { stopPropagation } from './stop-propagation';
-import { TextAreaUtils } from './text-area-utils';
-
-export interface TextLabelProps {
- font: string;
- text: string;
- color: string;
- onBlur?: () => void;
- onChange: (text: string) => void;
- offsetY?: number;
- offsetX?: number;
- scale?: number;
- isEditing?: boolean;
-}
-
-export const TextLabel = React.memo(function TextLabel({
- font,
- text,
- color,
- offsetX = 0,
- offsetY = 0,
- scale = 1,
- isEditing = false,
- onBlur,
- onChange,
-}: TextLabelProps) {
- const rInput = React.useRef(null);
- const rIsMounted = React.useRef(false);
-
- const handleChange = React.useCallback(
- (e: React.ChangeEvent) => {
- onChange(normalizeText(e.currentTarget.value));
- },
- [onChange]
- );
- const handleKeyDown = React.useCallback(
- (e: React.KeyboardEvent) => {
- if (e.key === 'Escape') return;
-
- if (e.key === 'Tab' && text.length === 0) {
- e.preventDefault();
- return;
- }
-
- if (!(e.key === 'Meta' || e.metaKey)) {
- e.stopPropagation();
- } else if (e.key === 'z' && e.metaKey) {
- if (e.shiftKey) {
- document.execCommand('redo', false);
- } else {
- document.execCommand('undo', false);
- }
- e.stopPropagation();
- e.preventDefault();
- return;
- }
-
- if (e.key === 'Tab') {
- e.preventDefault();
- if (e.shiftKey) {
- TextAreaUtils.unindent(e.currentTarget);
- } else {
- TextAreaUtils.indent(e.currentTarget);
- }
-
- onChange?.(normalizeText(e.currentTarget.value));
- }
- },
- [onChange]
- );
-
- const handleBlur = React.useCallback(
- (e: React.FocusEvent) => {
- e.currentTarget.setSelectionRange(0, 0);
- onBlur?.();
- },
- [onBlur]
- );
-
- const handleFocus = React.useCallback(
- (e: React.FocusEvent) => {
- if (!isEditing) return;
- if (!rIsMounted.current) return;
-
- if (document.activeElement === e.currentTarget) {
- e.currentTarget.select();
- }
- },
- [isEditing]
- );
-
- const handlePointerDown = React.useCallback<
- React.PointerEventHandler
- >(
- e => {
- if (isEditing) {
- e.stopPropagation();
- }
- },
- [isEditing]
- );
-
- React.useEffect(() => {
- if (isEditing) {
- requestAnimationFrame(() => {
- rIsMounted.current = true;
- const elm = rInput.current;
- if (elm) {
- elm.focus();
- elm.select();
- }
- });
- } else {
- onBlur?.();
- }
- }, [isEditing, onBlur]);
-
- const rInnerWrapper = React.useRef(null);
-
- React.useLayoutEffect(() => {
- const elm = rInnerWrapper.current;
- if (!elm) return;
- const size = getTextLabelSize(text, font);
- elm.style.transform = `scale(${scale}, ${scale}) translate(${offsetX}px, ${offsetY}px)`;
- elm.style.width = size[0] + 1 + 'px';
- elm.style.height = size[1] + 1 + 'px';
- }, [text, font, offsetY, offsetX, scale]);
-
- return (
-
-
- {isEditing ? (
-
- ) : (
- text
- )}
-
-
-
- );
-});
-
-const TextWrapper = styled('div')({
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- pointerEvents: 'none',
- userSelect: 'none',
- variants: {
- isGhost: {
- false: { opacity: 1 },
- true: { transition: 'opacity .2s', opacity: GHOSTED_OPACITY },
- },
- },
-});
-
-const InnerWrapper = styled('div')<{ hasText: boolean; isEditing: boolean }>({
- position: 'absolute',
- padding: '4px',
- zIndex: 1,
- minHeight: 1,
- minWidth: 1,
- lineHeight: 1,
- letterSpacing: LETTER_SPACING,
- outline: 0,
- fontWeight: '500',
- textAlign: 'center',
- backfaceVisibility: 'hidden',
- userSelect: 'none',
- WebkitUserSelect: 'none',
- WebkitTouchCallout: 'none',
- whiteSpace: 'pre-wrap',
- overflowWrap: 'break-word',
-
- variants: {
- hasText: {
- false: {
- pointerEvents: 'none',
- },
- true: {
- pointerEvents: 'all',
- },
- },
- isEditing: {
- false: {
- userSelect: 'none',
- },
- true: {
- background: '$boundsBg',
- userSelect: 'text',
- WebkitUserSelect: 'text',
- },
- },
- },
-});
-
-const TextArea = styled('textarea')({
- position: 'absolute',
- top: 0,
- left: 0,
- zIndex: 1,
- width: '100%',
- height: '100%',
- border: 'none',
- padding: '4px',
- resize: 'none',
- textAlign: 'inherit',
- minHeight: 'inherit',
- minWidth: 'inherit',
- lineHeight: 'inherit',
- letterSpacing: 'inherit',
- outline: 0,
- fontWeight: 'inherit',
- overflow: 'hidden',
- backfaceVisibility: 'hidden',
- display: 'inline-block',
- pointerEvents: 'all',
- background: '$boundsBg',
- userSelect: 'text',
- WebkitUserSelect: 'text',
- fontSmooth: 'always',
- WebkitFontSmoothing: 'subpixel-antialiased',
- MozOsxFontSmoothing: 'auto',
- whiteSpace: 'pre-wrap',
- overflowWrap: 'break-word',
-
- '&:focus': {
- outline: 'none',
- border: 'none',
- },
-});
diff --git a/libs/components/board-shapes/src/shared/transform-rectangle.ts b/libs/components/board-shapes/src/shared/transform-rectangle.ts
deleted file mode 100644
index 802a857ae..000000000
--- a/libs/components/board-shapes/src/shared/transform-rectangle.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import type { TLBounds, TLShape, TLTransformInfo } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-
-/**
- * Transform a rectangular shape.
- * @param shape
- * @param bounds
- * @param param2
- */
-export function transformRectangle(
- shape: T,
- bounds: TLBounds,
- { initialShape, transformOrigin, scaleX, scaleY }: TLTransformInfo
-) {
- if (shape.rotation || initialShape.isAspectRatioLocked) {
- const size = Vec.toFixed(
- Vec.mul(
- initialShape.size,
- Math.min(Math.abs(scaleX), Math.abs(scaleY))
- )
- );
- const point = Vec.toFixed([
- bounds.minX +
- (bounds.width - shape.size[0]) *
- (scaleX < 0 ? 1 - transformOrigin[0] : transformOrigin[0]),
- bounds.minY +
- (bounds.height - shape.size[1]) *
- (scaleY < 0 ? 1 - transformOrigin[1] : transformOrigin[1]),
- ]);
- const rotation =
- (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
- ? initialShape.rotation
- ? -initialShape.rotation
- : 0
- : initialShape.rotation;
- return {
- size,
- point,
- rotation,
- };
- } else {
- return {
- point: Vec.toFixed([bounds.minX, bounds.minY]),
- size: Vec.toFixed([bounds.width, bounds.height]),
- };
- }
-}
diff --git a/libs/components/board-shapes/src/shared/transform-single-rectangle.ts b/libs/components/board-shapes/src/shared/transform-single-rectangle.ts
deleted file mode 100644
index 10849b4d9..000000000
--- a/libs/components/board-shapes/src/shared/transform-single-rectangle.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { TLBounds, TLShape } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-
-/**
- * Transform a single rectangular shape.
- * @param shape
- * @param bounds
- */
-export function transformSingleRectangle<
- T extends TLShape & { size: number[] }
->(shape: T, bounds: TLBounds) {
- return {
- size: Vec.toFixed([bounds.width, bounds.height]),
- point: Vec.toFixed([bounds.minX, bounds.minY]),
- };
-}
diff --git a/libs/components/board-shapes/src/triangle-util/TriangleUtil.tsx b/libs/components/board-shapes/src/triangle-util/TriangleUtil.tsx
deleted file mode 100644
index 514287b1b..000000000
--- a/libs/components/board-shapes/src/triangle-util/TriangleUtil.tsx
+++ /dev/null
@@ -1,294 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectBoundsPolygon,
- intersectLineSegmentPolyline,
- intersectRayLineSegment,
-} from '@tldraw/intersect';
-import Vec from '@tldraw/vec';
-import {
- BINDING_DISTANCE,
- DashStyle,
- GHOSTED_OPACITY,
- LABEL_POINT,
- TDMeta,
- TDShape,
- TDShapeType,
- TriangleShape,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- getFontStyle,
- getShapeStyle,
- TextLabel,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { DashedTriangle } from './components/DashedTriangle';
-import { DrawTriangle } from './components/DrawTriangle';
-import { TriangleBindingIndicator } from './components/TriangleBindingIndicator';
-import { getTriangleCentroid, getTrianglePoints } from './triangle-helpers';
-
-type T = TriangleShape;
-type E = HTMLDivElement;
-
-export class TriangleUtil extends TDShapeUtil {
- type = TDShapeType.Triangle as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.Triangle,
- name: 'Triangle',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- bounds,
- isBinding,
- isEditing,
- isSelected,
- isGhost,
- meta,
- events,
- onShapeChange,
- onShapeBlur,
- },
- ref
- ) => {
- const {
- id,
- label = '',
- size,
- style,
- labelPoint = LABEL_POINT,
- } = shape;
- const font = getFontStyle(style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const Component =
- style.dash === DashStyle.Draw ? DrawTriangle : DashedTriangle;
- const handleLabelChange = React.useCallback(
- (label: string) => onShapeChange?.({ id, label }),
- [onShapeChange]
- );
- const offsetY = React.useMemo(() => {
- const center = Vec.div(size, 2);
- const centroid = getTriangleCentroid(size);
- return (centroid[1] - center[1]) * 0.72;
- }, [size]);
- return (
-
-
-
- {isBinding && }
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { size } = shape;
- return ;
- });
-
- private get_points(shape: T) {
- const {
- rotation = 0,
- point: [x, y],
- size: [w, h],
- } = shape;
- return [
- [x + w / 2, y],
- [x, y + h],
- [x + w, y + h],
- ].map(pt => Vec.rotWith(pt, this.getCenter(shape), rotation));
- }
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override getExpandedBounds = (shape: T) => {
- return Utils.getBoundsFromPoints(
- getTrianglePoints(shape.size, this.bindingDistance).map(pt =>
- Vec.add(pt, shape.point)
- )
- );
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- return intersectLineSegmentPolyline(A, B, this.get_points(shape))
- .didIntersect;
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds): boolean => {
- return (
- Utils.boundsContained(this.getBounds(shape), bounds) ||
- intersectBoundsPolygon(bounds, this.get_points(shape)).length > 0
- );
- };
-
- override getBindingPoint = (
- shape: T,
- fromShape: K,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- // Algorithm time! We need to find the binding point (a normalized point inside of the shape, or around the shape, where the arrow will point to) and the distance from the binding shape to the anchor.
-
- const expandedBounds = this.getExpandedBounds(shape);
-
- if (!Utils.pointInBounds(point, expandedBounds)) return;
-
- const points = getTrianglePoints(shape.size).map(pt =>
- Vec.add(pt, shape.point)
- );
-
- const expandedPoints = getTrianglePoints(
- shape.size,
- this.bindingDistance
- ).map(pt => Vec.add(pt, shape.point));
-
- const closestDistanceToEdge = Utils.pointsToLineSegments(points, true)
- .map(([a, b]) => Vec.distanceToLineSegment(a, b, point))
- .sort((a, b) => a - b)[0];
-
- if (
- !(
- Utils.pointInPolygon(point, expandedPoints) ||
- closestDistanceToEdge < this.bindingDistance
- )
- )
- return;
-
- const intersections = Utils.pointsToLineSegments(
- expandedPoints.concat([expandedPoints[0]])
- )
- .map(segment =>
- intersectRayLineSegment(
- origin,
- direction,
- segment[0],
- segment[1]
- )
- )
- .filter(intersection => intersection.didIntersect)
- .flatMap(intersection => intersection.points);
-
- if (!intersections.length) return;
-
- // The center of the triangle
- const center = Vec.add(getTriangleCentroid(shape.size), shape.point);
-
- // Find furthest intersection between ray from origin through point and expanded bounds. TODO: What if the shape has a curve? In that case, should we intersect the circle-from-three-points instead?
- const intersection = intersections.sort(
- (a, b) => Vec.dist(b, origin) - Vec.dist(a, origin)
- )[0];
-
- // The point between the handle and the intersection
- const middlePoint = Vec.med(point, intersection);
-
- let anchor: number[];
- let distance: number;
-
- if (bindAnywhere) {
- anchor =
- Vec.dist(point, center) < BINDING_DISTANCE / 2 ? center : point;
- distance = 0;
- } else {
- if (
- Vec.distanceToLineSegment(point, middlePoint, center) <
- BINDING_DISTANCE / 2
- ) {
- anchor = center;
- } else {
- anchor = middlePoint;
- }
-
- if (Utils.pointInPolygon(point, points)) {
- distance = this.bindingDistance;
- } else {
- distance = Math.max(
- this.bindingDistance,
- closestDistanceToEdge
- );
- }
- }
-
- const bindingPoint = Vec.divV(
- Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
-
- return {
- point: Vec.clampV(bindingPoint, 0, 1),
- distance,
- };
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/triangle-util/components/DashedTriangle.tsx b/libs/components/board-shapes/src/triangle-util/components/DashedTriangle.tsx
deleted file mode 100644
index b33914a43..000000000
--- a/libs/components/board-shapes/src/triangle-util/components/DashedTriangle.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import { getTrianglePoints } from '../triangle-helpers';
-
-interface TriangleSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DashedTriangle = React.memo(function DashedTriangle({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: TriangleSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const sw = 1 + strokeWidth * 1.618;
- const points = getTrianglePoints(size);
- const sides = Utils.pointsToLineSegments(points, true);
- const paths = sides.map(([start, end], i) => {
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- Vec.dist(start, end),
- strokeWidth * 1.618,
- style.dash
- );
-
- return (
-
- );
- });
-
- const bgPath = points.join();
-
- return (
- <>
-
- {style.isFilled && (
-
- )}
- {paths}
- >
- );
-});
diff --git a/libs/components/board-shapes/src/triangle-util/components/DrawTriangle.tsx b/libs/components/board-shapes/src/triangle-util/components/DrawTriangle.tsx
deleted file mode 100644
index 4cbfdf8ce..000000000
--- a/libs/components/board-shapes/src/triangle-util/components/DrawTriangle.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getTriangleIndicatorPathTDSnapshot,
- getTrianglePath,
-} from '../triangle-helpers';
-
-interface TriangleSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DrawTriangle = React.memo(function DrawTriangle({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: TriangleSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const path_td_snapshot = getTrianglePath(id, size, style);
- const indicatorPath = getTriangleIndicatorPathTDSnapshot(id, size, style);
- return (
- <>
-
- {style.isFilled && (
-
- )}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/triangle-util/components/TriangleBindingIndicator.tsx b/libs/components/board-shapes/src/triangle-util/components/TriangleBindingIndicator.tsx
deleted file mode 100644
index 078b53488..000000000
--- a/libs/components/board-shapes/src/triangle-util/components/TriangleBindingIndicator.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-import { getTrianglePoints } from '../triangle-helpers';
-
-interface TriangleBindingIndicatorProps {
- size: number[];
-}
-
-export function TriangleBindingIndicator({
- size,
-}: TriangleBindingIndicatorProps) {
- const trianglePoints = getTrianglePoints(size).join();
- return (
-
- );
-}
diff --git a/libs/components/board-shapes/src/triangle-util/index.ts b/libs/components/board-shapes/src/triangle-util/index.ts
deleted file mode 100644
index b5e9d8015..000000000
--- a/libs/components/board-shapes/src/triangle-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './TriangleUtil';
diff --git a/libs/components/board-shapes/src/triangle-util/triangle-helpers.ts b/libs/components/board-shapes/src/triangle-util/triangle-helpers.ts
deleted file mode 100644
index 892eb7eb1..000000000
--- a/libs/components/board-shapes/src/triangle-util/triangle-helpers.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import getStroke, { getStrokePoints } from 'perfect-freehand';
-import { getOffsetPolygon, getShapeStyle } from '../shared';
-
-export function getTrianglePoints(size: number[], offset = 0, rotation = 0) {
- const [w, h] = size;
- let points = [
- [w / 2, 0],
- [w, h],
- [0, h],
- ];
- if (offset) points = getOffsetPolygon(points, offset);
- if (rotation)
- points = points.map(pt => Vec.rotWith(pt, [w / 2, h / 2], rotation));
-
- return points;
-}
-
-export function getTriangleCentroid(size: number[]) {
- const [w, h] = size;
- const points = [
- [w / 2, 0],
- [w, h],
- [0, h],
- ];
- return [
- (points[0][0] + points[1][0] + points[2][0]) / 3,
- (points[0][1] + points[1][1] + points[2][1]) / 3,
- ];
-}
-
-function getTriangleDrawPoints(
- id: string,
- size: number[],
- strokeWidth: number
-) {
- const [w, h] = size;
- const getRandom = Utils.rng(id);
- // Random corner offsets
- const offsets = Array.from(Array(3)).map(() => {
- return [
- getRandom() * strokeWidth * 0.75,
- getRandom() * strokeWidth * 0.75,
- ];
- });
- // Corners
- const corners = [
- Vec.add([w / 2, 0], offsets[0]),
- Vec.add([w, h], offsets[1]),
- Vec.add([0, h], offsets[2]),
- ];
- // Which side to start drawing first
- const rm = Math.round(Math.abs(getRandom() * 2 * 3));
- // Number of points per side
- // Inset each line by the corner radii and let the freehand algo
- // interpolate points for the corners.
- const lines = Utils.rotateArray(
- [
- Vec.pointsBetween(corners[0], corners[1], 32),
- Vec.pointsBetween(corners[1], corners[2], 32),
- Vec.pointsBetween(corners[2], corners[0], 32),
- ],
- rm
- );
- // For the final points, include the first half of the first line again,
- // so that the line wraps around and avoids ending on a sharp corner.
- // This has a bit of finesse and magic—if you change the points between
- // function, then you'll likely need to change this one too.
- const points = [...lines.flat(), ...lines[0]];
- return {
- points,
- };
-}
-
-function getDrawStrokeInfo(id: string, size: number[], style: ShapeStyles) {
- const { strokeWidth } = getShapeStyle(style);
- const { points } = getTriangleDrawPoints(id, size, strokeWidth);
- const options = {
- size: strokeWidth,
- thinning: 0.65,
- streamline: 0.3,
- smoothing: 1,
- simulatePressure: false,
- last: true,
- };
- return { points, options };
-}
-
-export function getTrianglePath(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const stroke = getStroke(points, options);
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getTriangleIndicatorPathTDSnapshot(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const strokePoints = getStrokePoints(points, options);
- return Utils.getSvgPathFromStroke(
- strokePoints.map(pt => pt.point.slice(0, 2)),
- false
- );
-}
diff --git a/libs/components/board-shapes/src/video-util/index.ts b/libs/components/board-shapes/src/video-util/index.ts
deleted file mode 100644
index 25fabeece..000000000
--- a/libs/components/board-shapes/src/video-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './video-util';
diff --git a/libs/components/board-shapes/src/video-util/video-util.tsx b/libs/components/board-shapes/src/video-util/video-util.tsx
deleted file mode 100644
index 37ad390c2..000000000
--- a/libs/components/board-shapes/src/video-util/video-util.tsx
+++ /dev/null
@@ -1,271 +0,0 @@
-import { HTMLContainer, Utils } from '@tldraw/core';
-import {
- GHOSTED_OPACITY,
- TDMeta,
- TDShapeType,
- TDVideoAsset,
- VideoShape,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-
-type T = VideoShape;
-type E = HTMLDivElement;
-
-export class VideoUtil extends TDShapeUtil {
- type = TDShapeType.Video as const;
- override canBind = true;
- override canEdit = true;
- override canClone = true;
- override isAspectRatioLocked = true;
- override showCloneHandles = true;
- override isStateful = true; // don't unmount
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'video',
- type: TDShapeType.Video,
- name: 'Video',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- assetId: 'assetId',
- isPlaying: true,
- currentTime: 0,
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- asset = { src: '' },
- isBinding,
- isEditing,
- isGhost,
- meta,
- events,
- onShapeChange,
- },
- ref
- ) => {
- const rVideo = React.useRef(null);
- const rWrapper = React.useRef(null);
-
- const { currentTime = 0, size, isPlaying, style } = shape;
-
- React.useLayoutEffect(() => {
- const wrapper = rWrapper.current;
- if (!wrapper) return;
- const [width, height] = size;
- wrapper.style.width = `${width}px`;
- wrapper.style.height = `${height}px`;
- }, [size]);
-
- React.useLayoutEffect(() => {
- const video = rVideo.current;
- if (!video) return;
- if (isPlaying) video.play();
- // throws error on safari
- else video.pause();
- }, [isPlaying]);
-
- React.useLayoutEffect(() => {
- const video = rVideo.current;
- if (!video) return;
- if (currentTime !== video.currentTime) {
- video.currentTime = currentTime;
- }
- }, [currentTime]);
-
- const handlePlay = React.useCallback(() => {
- onShapeChange?.({ id: shape.id, isPlaying: true });
- }, []);
-
- const handlePause = React.useCallback(() => {
- onShapeChange?.({ id: shape.id, isPlaying: false });
- }, []);
-
- const handleSetCurrentTime = React.useCallback(() => {
- const video = rVideo.current;
- if (!video) return;
- if (!isEditing) return;
- onShapeChange?.({
- id: shape.id,
- currentTime: video.currentTime,
- });
- }, [isEditing]);
-
- return (
-
- {isBinding && (
-
- )}
-
-
-
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const {
- size: [width, height],
- } = shape;
-
- return (
-
- );
- });
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.isPlaying !== prev.isPlaying
- );
- };
-
- override getSvgElement = (shape: VideoShape) => {
- const bounds = this.getBounds(shape);
- const elm = document.createElementNS(
- 'http://www.w3.org/2000/svg',
- 'image'
- );
- elm.setAttribute('width', `${bounds.width}`);
- elm.setAttribute('height', `${bounds.height}`);
- elm.setAttribute('xmlns:xlink', `http://www.w3.org/1999/xlink`);
- return elm;
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const Wrapper = styled('div')<{
- isDarkMode: boolean;
- isFilled: boolean;
- isGhost: boolean;
-}>({
- pointerEvents: 'all',
- position: 'relative',
- fontSize: '2em',
- height: '100%',
- width: '100%',
- borderRadius: '3px',
- perspective: '800px',
- overflow: 'hidden',
- p: {
- userSelect: 'none',
- },
- img: {
- userSelect: 'none',
- },
- variants: {
- isGhost: {
- false: { opacity: 1 },
- true: { transition: 'opacity .2s', opacity: GHOSTED_OPACITY },
- },
- isFilled: {
- true: {},
- false: {},
- },
- isDarkMode: {
- true: {},
- false: {},
- },
- },
- compoundVariants: [
- {
- isFilled: true,
- isDarkMode: true,
- css: {
- boxShadow:
- '2px 3px 12px -2px rgba(0,0,0,.3), 1px 1px 4px rgba(0,0,0,.3), 1px 1px 2px rgba(0,0,0,.3)',
- },
- },
- {
- isFilled: true,
- isDarkMode: false,
- css: {
- boxShadow:
- '2px 3px 12px -2px rgba(0,0,0,.2), 1px 1px 4px rgba(0,0,0,.16), 1px 1px 2px rgba(0,0,0,.16)',
- },
- },
- ],
-});
-
-const VideoElement = styled('video')({
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- maxWidth: '100%',
- minWidth: '100%',
- pointerEvents: 'none',
- objectFit: 'cover',
- userSelect: 'none',
- borderRadius: 2,
-});
diff --git a/libs/components/board-shapes/src/white-arrow-util/WhiteArrowUtil.tsx b/libs/components/board-shapes/src/white-arrow-util/WhiteArrowUtil.tsx
deleted file mode 100644
index b767dde96..000000000
--- a/libs/components/board-shapes/src/white-arrow-util/WhiteArrowUtil.tsx
+++ /dev/null
@@ -1,301 +0,0 @@
-import { SVGContainer, TLBounds, Utils } from '@tldraw/core';
-import {
- intersectBoundsPolygon,
- intersectLineSegmentPolyline,
- intersectRayLineSegment,
-} from '@tldraw/intersect';
-import Vec from '@tldraw/vec';
-import {
- BINDING_DISTANCE,
- DashStyle,
- GHOSTED_OPACITY,
- LABEL_POINT,
- TDMeta,
- TDShape,
- TDShapeType,
- WhiteArrowShape,
-} from '@toeverything/components/board-types';
-import { styled } from '@toeverything/components/ui';
-import * as React from 'react';
-import {
- defaultStyle,
- getBoundsRectangle,
- getFontStyle,
- getShapeStyle,
- TextLabel,
- transformRectangle,
- transformSingleRectangle,
-} from '../shared';
-import { TDShapeUtil } from '../TDShapeUtil';
-import { DashedWhiteArrow } from './components/DashedWhiteArrow';
-import { DrawWhiteArrow } from './components/DrawWhiteArrow';
-import { WhiteArrowBindingIndicator } from './components/WhiteArrowBindingIndicator';
-import {
- getWhiteArrowCentroid,
- getWhiteArrowPoints,
-} from './white-arrow-helpers';
-
-type T = WhiteArrowShape;
-type E = HTMLDivElement;
-
-export class WhiteArrowUtil extends TDShapeUtil {
- type = TDShapeType.WhiteArrow as const;
-
- override canBind = true;
-
- override canClone = true;
-
- override canEdit = true;
-
- getShape = (props: Partial): T => {
- return Utils.deepMerge(
- {
- id: 'id',
- type: TDShapeType.WhiteArrow,
- name: 'Triangle',
- parentId: 'page',
- childIndex: 1,
- point: [0, 0],
- size: [1, 1],
- rotation: 0,
- style: defaultStyle,
- label: '',
- labelPoint: [0.5, 0.5],
- workspace: props.workspace,
- },
- props
- );
- };
-
- Component = TDShapeUtil.Component(
- (
- {
- shape,
- bounds,
- isBinding,
- isEditing,
- isSelected,
- isGhost,
- meta,
- events,
- onShapeChange,
- onShapeBlur,
- },
- ref
- ) => {
- const {
- id,
- label = '',
- size,
- style,
- labelPoint = LABEL_POINT,
- } = shape;
- const font = getFontStyle(style);
- const styles = getShapeStyle(style, meta.isDarkMode);
- const Component =
- style.dash === DashStyle.Draw
- ? DrawWhiteArrow
- : DashedWhiteArrow;
- const handleLabelChange = React.useCallback(
- (label: string) => onShapeChange?.({ id, label }),
- [onShapeChange]
- );
- const offsetY = React.useMemo(() => {
- const center = Vec.div(size, 2);
- const centroid = getWhiteArrowCentroid(size);
- return (centroid[1] - center[1]) * 0.72;
- }, [size]);
- return (
-
-
-
- {isBinding && (
-
- )}
-
-
-
- );
- }
- );
-
- Indicator = TDShapeUtil.Indicator(({ shape }) => {
- const { size } = shape;
- return ;
- });
-
- private get_points(shape: T) {
- const {
- rotation = 0,
- point: [x, y],
- size: [w, h],
- } = shape;
- return [
- [x + w / 2, y],
- [x, y + h],
- [x + w, y + h],
- ].map(pt => Vec.rotWith(pt, this.getCenter(shape), rotation));
- }
-
- override shouldRender = (prev: T, next: T) => {
- return (
- next.size !== prev.size ||
- next.style !== prev.style ||
- next.label !== prev.label
- );
- };
-
- getBounds = (shape: T) => {
- return getBoundsRectangle(shape, this.boundsCache);
- };
-
- override getExpandedBounds = (shape: T) => {
- return Utils.getBoundsFromPoints(
- getWhiteArrowPoints(shape.size, this.bindingDistance).map(pt =>
- Vec.add(pt, shape.point)
- )
- );
- };
-
- override hitTestLineSegment = (
- shape: T,
- A: number[],
- B: number[]
- ): boolean => {
- return intersectLineSegmentPolyline(A, B, this.get_points(shape))
- .didIntersect;
- };
-
- override hitTestBounds = (shape: T, bounds: TLBounds): boolean => {
- return (
- Utils.boundsContained(this.getBounds(shape), bounds) ||
- intersectBoundsPolygon(bounds, this.get_points(shape)).length > 0
- );
- };
-
- override getBindingPoint = (
- shape: T,
- fromShape: K,
- point: number[],
- origin: number[],
- direction: number[],
- bindAnywhere: boolean
- ) => {
- // Algorithm time! We need to find the binding point (a normalized point inside of the shape, or around the shape, where the arrow will point to) and the distance from the binding shape to the anchor.
-
- const expandedBounds = this.getExpandedBounds(shape);
-
- if (!Utils.pointInBounds(point, expandedBounds)) return;
-
- const points = getWhiteArrowPoints(shape.size).map(pt =>
- Vec.add(pt, shape.point)
- );
-
- const expandedPoints = getWhiteArrowPoints(
- shape.size,
- this.bindingDistance
- ).map(pt => Vec.add(pt, shape.point));
-
- const closestDistanceToEdge = Utils.pointsToLineSegments(points, true)
- .map(([a, b]) => Vec.distanceToLineSegment(a, b, point))
- .sort((a, b) => a - b)[0];
-
- if (
- !(
- Utils.pointInPolygon(point, expandedPoints) ||
- closestDistanceToEdge < this.bindingDistance
- )
- )
- return;
-
- const intersections = Utils.pointsToLineSegments(
- expandedPoints.concat([expandedPoints[0]])
- )
- .map(segment =>
- intersectRayLineSegment(
- origin,
- direction,
- segment[0],
- segment[1]
- )
- )
- .filter(intersection => intersection.didIntersect)
- .flatMap(intersection => intersection.points);
-
- if (!intersections.length) return;
-
- // The center of the triangle
- const center = Vec.add(getWhiteArrowCentroid(shape.size), shape.point);
-
- // Find furthest intersection between ray from origin through point and expanded bounds. TODO: What if the shape has a curve? In that case, should we intersect the circle-from-three-points instead?
- const intersection = intersections.sort(
- (a, b) => Vec.dist(b, origin) - Vec.dist(a, origin)
- )[0];
-
- // The point between the handle and the intersection
- const middlePoint = Vec.med(point, intersection);
-
- let anchor: number[];
- let distance: number;
-
- if (bindAnywhere) {
- anchor =
- Vec.dist(point, center) < BINDING_DISTANCE / 2 ? center : point;
- distance = 0;
- } else {
- if (
- Vec.distanceToLineSegment(point, middlePoint, center) <
- BINDING_DISTANCE / 2
- ) {
- anchor = center;
- } else {
- anchor = middlePoint;
- }
-
- if (Utils.pointInPolygon(point, points)) {
- distance = this.bindingDistance;
- } else {
- distance = Math.max(
- this.bindingDistance,
- closestDistanceToEdge
- );
- }
- }
-
- const bindingPoint = Vec.divV(
- Vec.sub(anchor, [expandedBounds.minX, expandedBounds.minY]),
- [expandedBounds.width, expandedBounds.height]
- );
-
- return {
- point: Vec.clampV(bindingPoint, 0, 1),
- distance,
- };
- };
-
- override transform = transformRectangle;
-
- override transformSingle = transformSingleRectangle;
-}
-
-const FullWrapper = styled('div')({ width: '100%', height: '100%' });
diff --git a/libs/components/board-shapes/src/white-arrow-util/components/DashedWhiteArrow.tsx b/libs/components/board-shapes/src/white-arrow-util/components/DashedWhiteArrow.tsx
deleted file mode 100644
index 16e3e1254..000000000
--- a/libs/components/board-shapes/src/white-arrow-util/components/DashedWhiteArrow.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import { getWhiteArrowPoints } from '../white-arrow-helpers';
-
-interface WhiteArrowSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DashedWhiteArrow = React.memo(function DashedWhiteArrow({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: WhiteArrowSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const sw = 1 + strokeWidth * 1.618;
- const points = getWhiteArrowPoints(size);
- const sides = Utils.pointsToLineSegments(points, true);
- const paths = sides.map(([start, end], i) => {
- const { strokeDasharray, strokeDashoffset } = Utils.getPerfectDashProps(
- Vec.dist(start, end),
- strokeWidth * 1.618,
- style.dash
- );
-
- return (
-
- );
- });
-
- const bgPath = points.join();
-
- return (
- <>
-
- {style.isFilled && (
-
- )}
- {paths}
- >
- );
-});
diff --git a/libs/components/board-shapes/src/white-arrow-util/components/DrawWhiteArrow.tsx b/libs/components/board-shapes/src/white-arrow-util/components/DrawWhiteArrow.tsx
deleted file mode 100644
index ff404c79d..000000000
--- a/libs/components/board-shapes/src/white-arrow-util/components/DrawWhiteArrow.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import * as React from 'react';
-import { getShapeStyle } from '../../shared';
-import {
- getWhiteArrowIndicatorPathTDSnapshot,
- getWhiteArrowPath,
-} from '../white-arrow-helpers';
-
-interface WhiteArrowSvgProps {
- id: string;
- size: number[];
- style: ShapeStyles;
- isSelected: boolean;
- isDarkMode: boolean;
-}
-
-export const DrawWhiteArrow = React.memo(function DrawTriangle({
- id,
- size,
- style,
- isSelected,
- isDarkMode,
-}: WhiteArrowSvgProps) {
- const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode);
- const path_td_snapshot = getWhiteArrowPath(id, size, style);
- const indicatorPath = getWhiteArrowIndicatorPathTDSnapshot(id, size, style);
- return (
- <>
-
- {style.isFilled && (
-
- )}
-
- >
- );
-});
diff --git a/libs/components/board-shapes/src/white-arrow-util/components/WhiteArrowBindingIndicator.tsx b/libs/components/board-shapes/src/white-arrow-util/components/WhiteArrowBindingIndicator.tsx
deleted file mode 100644
index d8bac6163..000000000
--- a/libs/components/board-shapes/src/white-arrow-util/components/WhiteArrowBindingIndicator.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { BINDING_DISTANCE } from '@toeverything/components/board-types';
-import { getWhiteArrowPoints } from '../white-arrow-helpers';
-
-interface TriangleBindingIndicatorProps {
- size: number[];
-}
-
-export function WhiteArrowBindingIndicator({
- size,
-}: TriangleBindingIndicatorProps) {
- const trianglePoints = getWhiteArrowPoints(size).join();
- return (
-
- );
-}
diff --git a/libs/components/board-shapes/src/white-arrow-util/index.ts b/libs/components/board-shapes/src/white-arrow-util/index.ts
deleted file mode 100644
index b9985e168..000000000
--- a/libs/components/board-shapes/src/white-arrow-util/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './WhiteArrowUtil';
diff --git a/libs/components/board-shapes/src/white-arrow-util/white-arrow-helpers.ts b/libs/components/board-shapes/src/white-arrow-util/white-arrow-helpers.ts
deleted file mode 100644
index 587dbd251..000000000
--- a/libs/components/board-shapes/src/white-arrow-util/white-arrow-helpers.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { Utils } from '@tldraw/core';
-import Vec from '@tldraw/vec';
-import type { ShapeStyles } from '@toeverything/components/board-types';
-import getStroke, { getStrokePoints } from 'perfect-freehand';
-import { getOffsetPolygon, getShapeStyle } from '../shared';
-function getPonits(w: number, h: number) {
- return [
- [w / 2, 0],
- [w, h / 2],
- [(w / 4) * 3, h / 2],
- [(w / 4) * 3, h],
- [w / 4, h],
- [w / 4, h / 2],
- [0, h / 2],
- ];
-}
-
-export function getWhiteArrowPoints(size: number[], offset = 0, rotation = 0) {
- const [w, h] = size;
- let points = getPonits(w, h);
- if (offset) points = getOffsetPolygon(points, offset);
- if (rotation)
- points = points.map(pt => Vec.rotWith(pt, [w / 2, h / 2], rotation));
-
- return points;
-}
-
-export function getWhiteArrowCentroid(size: number[]) {
- const [w, h] = size;
- const points = getPonits(w, h);
- return [
- (points[0][0] + points[1][0] + points[2][0]) / 3,
- (points[0][1] + points[1][1] + points[2][1]) / 3,
- ];
-}
-
-function getWhiteArrowDrawPoints(
- id: string,
- size: number[],
- strokeWidth: number
-) {
- const [w, h] = size;
- const getRandom = Utils.rng(id);
- // Random corner offsets
- const offsets = Array.from(Array(7)).map(() => {
- return [
- getRandom() * strokeWidth * 0.75,
- getRandom() * strokeWidth * 0.75,
- ];
- });
- // Corners
- const point = getPonits(w, h);
- const corners = [
- Vec.add(point[0], offsets[0]),
- Vec.add(point[1], offsets[1]),
- Vec.add(point[2], offsets[2]),
- Vec.add(point[3], offsets[3]),
- Vec.add(point[4], offsets[4]),
- Vec.add(point[5], offsets[5]),
- Vec.add(point[6], offsets[6]),
- ];
-
- // Which side to start drawing first
- const rm = Math.round(Math.abs(getRandom() * 2 * 3));
- // Number of points per side
- // Inset each line by the corner radii and let the freehand algo
- // interpolate points for the corners.
- const lines = Utils.rotateArray(
- [
- Vec.pointsBetween(corners[0], corners[1], 32),
- Vec.pointsBetween(corners[1], corners[2], 32),
- Vec.pointsBetween(corners[2], corners[3], 32),
- Vec.pointsBetween(corners[3], corners[4], 32),
- Vec.pointsBetween(corners[4], corners[5], 32),
- Vec.pointsBetween(corners[5], corners[6], 32),
- Vec.pointsBetween(corners[6], corners[0], 32),
- ],
- rm
- );
- // For the final points, include the first half of the first line again,
- // so that the line wraps around and avoids ending on a sharp corner.
- // This has a bit of finesse and magic—if you change the points between
- // function, then you'll likely need to change this one too.
- const points = [...lines.flat(), ...lines[0]];
- return {
- points,
- };
-}
-
-function getDrawStrokeInfo(id: string, size: number[], style: ShapeStyles) {
- const { strokeWidth } = getShapeStyle(style);
- const { points } = getWhiteArrowDrawPoints(id, size, strokeWidth);
- const options = {
- size: strokeWidth,
- thinning: 0.65,
- streamline: 0.3,
- smoothing: 1,
- simulatePressure: false,
- last: true,
- };
- return { points, options };
-}
-
-export function getWhiteArrowPath(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const stroke = getStroke(points, options);
- return Utils.getSvgPathFromStroke(stroke);
-}
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export function getWhiteArrowIndicatorPathTDSnapshot(
- id: string,
- size: number[],
- style: ShapeStyles
-) {
- const { points, options } = getDrawStrokeInfo(id, size, style);
- const strokePoints = getStrokePoints(points, options);
- return Utils.getSvgPathFromStroke(
- strokePoints.map(pt => pt.point.slice(0, 2)),
- false
- );
-}
diff --git a/libs/components/board-shapes/tsconfig.json b/libs/components/board-shapes/tsconfig.json
deleted file mode 100644
index dce3dae9a..000000000
--- a/libs/components/board-shapes/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "extends": "../../../tsconfig.base.json",
- "compilerOptions": {
- "jsx": "react-jsx",
- "allowJs": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "forceConsistentCasingInFileNames": true,
- "strict": true,
- "noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": true,
- "noImplicitReturns": true,
- "noFallthroughCasesInSwitch": true
- },
- "files": [],
- "include": [],
- "references": [
- {
- "path": "./tsconfig.lib.json"
- },
- {
- "path": "./tsconfig.spec.json"
- }
- ]
-}
diff --git a/libs/components/board-shapes/tsconfig.lib.json b/libs/components/board-shapes/tsconfig.lib.json
deleted file mode 100644
index 6a4378740..000000000
--- a/libs/components/board-shapes/tsconfig.lib.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "types": ["node"]
- },
- "files": [
- "../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
- "../../../node_modules/@nrwl/react/typings/image.d.ts"
- ],
- "exclude": [
- "jest.config.ts",
- "**/*.spec.ts",
- "**/*.test.ts",
- "**/*.spec.tsx",
- "**/*.test.tsx",
- "**/*.spec.js",
- "**/*.test.js",
- "**/*.spec.jsx",
- "**/*.test.jsx"
- ],
- "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
-}
diff --git a/libs/components/board-shapes/tsconfig.spec.json b/libs/components/board-shapes/tsconfig.spec.json
deleted file mode 100644
index 24b3ac659..000000000
--- a/libs/components/board-shapes/tsconfig.spec.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"]
- },
- "include": [
- "jest.config.ts",
- "**/*.test.ts",
- "**/*.spec.ts",
- "**/*.test.tsx",
- "**/*.spec.tsx",
- "**/*.test.js",
- "**/*.spec.js",
- "**/*.test.jsx",
- "**/*.spec.jsx",
- "**/*.d.ts"
- ]
-}
diff --git a/libs/components/board-state/.babelrc b/libs/components/board-state/.babelrc
deleted file mode 100644
index ac4757ac3..000000000
--- a/libs/components/board-state/.babelrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presets": [
- [
- "@nrwl/react/babel",
- {
- "runtime": "automatic",
- "useBuiltIns": "usage"
- }
- ]
- ],
- "plugins": []
-}
diff --git a/libs/components/board-state/.eslintrc.json b/libs/components/board-state/.eslintrc.json
deleted file mode 100644
index bd2c1567c..000000000
--- a/libs/components/board-state/.eslintrc.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"],
- "ignorePatterns": ["!**/*"],
- "overrides": [
- {
- "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
- "rules": {}
- },
- {
- "files": ["*.ts", "*.tsx"],
- "rules": {}
- },
- {
- "files": ["*.js", "*.jsx"],
- "rules": {}
- }
- ]
-}
diff --git a/libs/components/board-state/README.md b/libs/components/board-state/README.md
deleted file mode 100644
index 76b25970f..000000000
--- a/libs/components/board-state/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# components-board-state
-
-This library was generated with [Nx](https://nx.dev).
-
-## Running unit tests
-
-Run `nx test components-board-state` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/components/board-state/jest.config.ts b/libs/components/board-state/jest.config.ts
deleted file mode 100644
index 5a1ce0621..000000000
--- a/libs/components/board-state/jest.config.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/* eslint-disable */
-export default {
- displayName: 'components-board-state',
- preset: '../../../jest.preset.js',
- transform: {
- '^.+\\.[tj]sx?$': 'babel-jest',
- },
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
- coverageDirectory: '../../../coverage/libs/components/board-state',
-};
diff --git a/libs/components/board-state/package.json b/libs/components/board-state/package.json
deleted file mode 100644
index 62dc7aeef..000000000
--- a/libs/components/board-state/package.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "name": "@toeverything/components/board-state",
- "version": "0.0.1",
- "license": "MIT",
- "dependencies": {
- "@tldraw/core": "^1.12.0",
- "@tldraw/intersect": "^1.7.1",
- "@tldraw/vec": "^1.7.0",
- "idb-keyval": "^6.2.0",
- "zustand": "^3.7.2"
- }
-}
diff --git a/libs/components/board-state/project.json b/libs/components/board-state/project.json
deleted file mode 100644
index ffd6ab13c..000000000
--- a/libs/components/board-state/project.json
+++ /dev/null
@@ -1,45 +0,0 @@
-{
- "$schema": "../../../node_modules/nx/schemas/project-schema.json",
- "sourceRoot": "libs/components/board-state/src",
- "projectType": "library",
- "tags": ["components"],
- "targets": {
- "build": {
- "executor": "@nrwl/web:rollup",
- "outputs": ["{options.outputPath}"],
- "options": {
- "outputPath": "dist/libs/components/board-state",
- "tsConfig": "libs/components/board-state/tsconfig.lib.json",
- "project": "libs/components/board-state/package.json",
- "entryFile": "libs/components/board-state/src/index.ts",
- "external": ["react/jsx-runtime"],
- "rollupConfig": "libs/rollup.config.cjs",
- "compiler": "babel",
- "assets": [
- {
- "glob": "libs/components/board-state/README.md",
- "input": ".",
- "output": "."
- }
- ]
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": [
- "libs/components/board-state/**/*.{ts,tsx,js,jsx}"
- ]
- }
- },
- "test": {
- "executor": "@nrwl/jest:jest",
- "outputs": ["coverage/libs/components/board-state"],
- "options": {
- "jestConfig": "libs/components/board-state/jest.config.ts",
- "passWithNoTests": true
- }
- }
- }
-}
diff --git a/libs/components/board-state/src/data/browser-fs-access/directory-open.js b/libs/components/board-state/src/data/browser-fs-access/directory-open.js
deleted file mode 100644
index 81262c27d..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/directory-open.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-import supported from './supported.js';
-
-const implementation = !supported
- ? import('./legacy/directory-open.js')
- : import('./fs-access/directory-open.js');
-
-/**
- * For opening directories, dynamically either loads the File System Access API
- * module or the legacy method.
- */
-export async function directoryOpen(...args) {
- return (await implementation).default(...args);
-}
diff --git a/libs/components/board-state/src/data/browser-fs-access/file-open.js b/libs/components/board-state/src/data/browser-fs-access/file-open.js
deleted file mode 100644
index 0b5ccd377..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/file-open.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-import supported from './supported.js';
-
-const implementation = !supported
- ? import('./legacy/file-open.js')
- : import('./fs-access/file-open.js');
-
-/**
- * For opening files, dynamically either loads the File System Access API module
- * or the legacy method.
- */
-export async function fileOpen(...args) {
- return (await implementation).default(...args);
-}
diff --git a/libs/components/board-state/src/data/browser-fs-access/file-save.js b/libs/components/board-state/src/data/browser-fs-access/file-save.js
deleted file mode 100644
index 54cfe6545..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/file-save.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-import supported from './supported.js';
-
-const implementation = !supported
- ? import('./legacy/file-save.js')
- : import('./fs-access/file-save.js');
-
-/**
- * For saving files, dynamically either loads the File System Access API module
- * or the legacy method.
- */
-export async function fileSave(...args) {
- return (await implementation).default(...args);
-}
diff --git a/libs/components/board-state/src/data/browser-fs-access/fs-access/directory-open.js b/libs/components/board-state/src/data/browser-fs-access/fs-access/directory-open.js
deleted file mode 100644
index 3f6a3b62e..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/fs-access/directory-open.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-const getFiles = async (
- dirHandle,
- recursive,
- path = dirHandle.name,
- skipDirectory
-) => {
- const dirs = [];
- const files = [];
- for (const entry of dirHandle.values()) {
- const nestedPath = `${path}/${entry.name}`;
- if (entry.kind === 'file') {
- files.push(
- await entry.getFile().then(file => {
- file.directoryHandle = dirHandle;
- return Object.defineProperty(file, 'webkitRelativePath', {
- configurable: true,
- enumerable: true,
- get: () => nestedPath,
- });
- })
- );
- } else if (
- entry.kind === 'directory' &&
- recursive &&
- (!skipDirectory || !skipDirectory(entry))
- ) {
- dirs.push(
- await getFiles(entry, recursive, nestedPath, skipDirectory)
- );
- }
- }
- return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))];
-};
-
-/**
- * Opens a directory from disk using the File System Access API.
- * @type { typeof import("../../index").directoryOpen }
- */
-export default async (options = {}) => {
- options.recursive = options.recursive || false;
- const handle = await window.showDirectoryPicker({
- id: options.id,
- startIn: options.startIn,
- });
- return getFiles(
- handle,
- options.recursive,
- undefined,
- options.skipDirectory
- );
-};
diff --git a/libs/components/board-state/src/data/browser-fs-access/fs-access/file-open.js b/libs/components/board-state/src/data/browser-fs-access/fs-access/file-open.js
deleted file mode 100644
index 14d082be6..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/fs-access/file-open.js
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-const getFileWithHandle = async handle => {
- const file = await handle.getFile();
- file.handle = handle;
- return file;
-};
-
-/**
- * Opens a file from disk using the File System Access API.
- * @type { typeof import("../../index").fileOpen }
- */
-export default async (options = [{}]) => {
- if (!Array.isArray(options)) {
- options = [options];
- }
- const types = [];
- options.forEach((option, i) => {
- types[i] = {
- description: option.description || '',
- accept: {},
- };
- if (option.mimeTypes) {
- option.mimeTypes.map(mimeType => {
- types[i].accept[mimeType] = option.extensions || [];
- });
- } else {
- types[i].accept['*/*'] = option.extensions || [];
- }
- });
- const handleOrHandles = await window.showOpenFilePicker({
- id: options[0].id,
- startIn: options[0].startIn,
- types,
- multiple: options[0].multiple || false,
- excludeAcceptAllOption: options[0].excludeAcceptAllOption || false,
- });
- const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
- if (options[0].multiple) {
- return files;
- }
- return files[0];
-};
diff --git a/libs/components/board-state/src/data/browser-fs-access/fs-access/file-save.js b/libs/components/board-state/src/data/browser-fs-access/fs-access/file-save.js
deleted file mode 100644
index 44eb8e782..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/fs-access/file-save.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-/**
- * Saves a file to disk using the File System Access API.
- * @type { typeof import("../../index").fileSave }
- */
-export default async (
- blobOrResponse,
- options = [{}],
- existingHandle = null,
- throwIfExistingHandleNotGood = false
-) => {
- if (!Array.isArray(options)) {
- options = [options];
- }
- options[0].fileName = options[0].fileName || 'Untitled';
- const types = [];
- options.forEach((option, i) => {
- types[i] = {
- description: option.description || '',
- accept: {},
- };
- if (option.mimeTypes) {
- if (i === 0) {
- if (blobOrResponse.type) {
- option.mimeTypes.push(blobOrResponse.type);
- } else if (
- blobOrResponse.headers &&
- blobOrResponse.headers.get('content-type')
- ) {
- option.mimeTypes.push(
- blobOrResponse.headers.get('content-type')
- );
- }
- }
- option.mimeTypes.map(mimeType => {
- types[i].accept[mimeType] = option.extensions || [];
- });
- } else if (blobOrResponse.type) {
- types[i].accept[blobOrResponse.type] = option.extensions || [];
- }
- });
- if (existingHandle) {
- try {
- // Check if the file still exists.
- await existingHandle.getFile();
- } catch (err) {
- existingHandle = null;
- if (throwIfExistingHandleNotGood) {
- throw err;
- }
- }
- }
- const handle =
- existingHandle ||
- (await window.showSaveFilePicker({
- suggestedName: options[0].fileName,
- id: options[0].id,
- startIn: options[0].startIn,
- types,
- excludeAcceptAllOption: options[0].excludeAcceptAllOption || false,
- }));
- const writable = await handle.createWritable();
- // Use streaming on the `Blob` if the browser supports it.
- if ('stream' in blobOrResponse) {
- const stream = blobOrResponse.stream();
- await stream.pipeTo(writable);
- return handle;
- // Handle passed `ReadableStream`.
- } else if ('body' in blobOrResponse) {
- await blobOrResponse.body.pipeTo(writable);
- return handle;
- }
- // Default case of `Blob` passed and `Blob.stream()` not supported.
- await writable.write(blobOrResponse);
- await writable.close();
- return handle;
-};
diff --git a/libs/components/board-state/src/data/browser-fs-access/index.d.ts b/libs/components/board-state/src/data/browser-fs-access/index.d.ts
deleted file mode 100644
index 505406859..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/index.d.ts
+++ /dev/null
@@ -1,242 +0,0 @@
-/**
- * Properties shared by all `options` provided to file save and open operations
- */
-export interface CoreFileOptions {
- /** Acceptable file extensions. Defaults to [""]. */
- extensions?: string[];
- /** Suggested file description. Defaults to "". */
- description?: string;
- /** Acceptable MIME types. [] */
- mimeTypes?: string[];
-}
-
-/**
- * Properties shared by the _first_ `options` object provided to file save and
- * open operations (any additional options objects provided to those operations
- * cannot have these properties)
- */
-export interface FirstCoreFileOptions extends CoreFileOptions {
- startIn?: WellKnownDirectory | FileSystemHandle;
- /** By specifying an ID, the user agent can remember different directories for different IDs. */
- id?: string;
- excludeAcceptAllOption?: boolean | false;
-}
-
-/**
- * The first `options` object passed to file save operations can also specify
- * a filename
- */
-export interface FirstFileSaveOptions extends FirstCoreFileOptions {
- /** Suggested file name. Defaults to "Untitled". */
- fileName?: string;
- /**
- * Configurable cleanup and `Promise` rejector usable with legacy API for
- * determining when (and reacting if) a user cancels the operation. The
- * method will be passed a reference to the internal `rejectionHandler` that
- * can, e.g., be attached to/removed from the window or called after a
- * timeout. The method should return a function that will be called when
- * either the user chooses to open a file or the `rejectionHandler` is
- * called. In the latter case, the returned function will also be passed a
- * reference to the `reject` callback for the `Promise` returned by
- * `fileOpen`, so that developers may reject the `Promise` when desired at
- * that time.
- * Example rejector:
- *
- * const file = await fileOpen({
- * legacySetup: (rejectionHandler) => {
- * const timeoutId = setTimeout(rejectionHandler, 10_000);
- * return (reject) => {
- * clearTimeout(timeoutId);
- * if (reject) {
- * reject('My error message here.');
- * }
- * };
- * },
- * });
- *
- * ToDo: Remove this workaround once
- * https://github.com/whatwg/html/issues/6376 is specified and supported.
- */
- legacySetup?: (
- resolve: (value: Blob) => void,
- rejectionHandler: () => void,
- anchor: HTMLAnchorElement
- ) => (reject?: (reason?: any) => void) => void;
-}
-
-/**
- * The first `options` object passed to file open operations can specify
- * whether multiple files can be selected (the return type of the operation
- * will be updated appropriately) and a way of handling cleanup and rejection
- * for legacy open operations.
- */
-export interface FirstFileOpenOptions
- extends FirstCoreFileOptions {
- /** Allow multiple files to be selected. Defaults to false. */
- multiple?: M;
- /**
- * Configurable cleanup and `Promise` rejector usable with legacy API for
- * determining when (and reacting if) a user cancels the operation. The
- * method will be passed a reference to the internal `rejectionHandler` that
- * can, e.g., be attached to/removed from the window or called after a
- * timeout. The method should return a function that will be called when
- * either the user chooses to open a file or the `rejectionHandler` is
- * called. In the latter case, the returned function will also be passed a
- * reference to the `reject` callback for the `Promise` returned by
- * `fileOpen`, so that developers may reject the `Promise` when desired at
- * that time.
- * Example rejector:
- *
- * const file = await fileOpen({
- * legacySetup: (rejectionHandler) => {
- * const timeoutId = setTimeout(rejectionHandler, 10_000);
- * return (reject) => {
- * clearTimeout(timeoutId);
- * if (reject) {
- * reject('My error message here.');
- * }
- * };
- * },
- * });
- *
- * ToDo: Remove this workaround once
- * https://github.com/whatwg/html/issues/6376 is specified and supported.
- */
- legacySetup?: (
- resolve: (
- value: M extends false | undefined
- ? FileWithHandle
- : FileWithHandle[]
- ) => void,
- rejectionHandler: () => void,
- input: HTMLInputElement
- ) => (reject?: (reason?: any) => void) => void;
-}
-
-/**
- * Opens file(s) from disk.
- */
-export function fileOpen(
- options?:
- | [FirstFileOpenOptions, ...CoreFileOptions[]]
- | FirstFileOpenOptions
-): M extends false | undefined
- ? Promise
- : Promise;
-
-export type WellKnownDirectory =
- | 'desktop'
- | 'documents'
- | 'downloads'
- | 'music'
- | 'pictures'
- | 'videos';
-
-/**
- * Saves a file to disk.
- * @returns Optional file handle to save in place.
- */
-export function fileSave(
- /** To-be-saved `Blob` or `Response` */
- blobOrResponse: Blob | Response,
- options?:
- | [FirstFileSaveOptions, ...CoreFileOptions[]]
- | FirstFileSaveOptions,
- /**
- * A potentially existing file handle for a file to save to. Defaults to
- * null.
- */
- existingHandle?: FileSystemHandle | null,
- /**
- * Determines whether to throw (rather than open a new file save dialog)
- * when existingHandle is no longer good. Defaults to false.
- */
- throwIfExistingHandleNotGood?: boolean | false
-): Promise;
-
-/**
- * Opens a directory from disk using the File System Access API.
- * @returns Contained files.
- */
-export function directoryOpen(options?: {
- /** Whether to recursively get subdirectories. */
- recursive: boolean;
- /** Suggested directory in which the file picker opens. */
- startIn?: WellKnownDirectory | FileSystemHandle;
- /** By specifying an ID, the user agent can remember different directories for different IDs. */
- id?: string;
- /** Callback to determine whether a directory should be entered, return `true` to skip. */
- skipDirectory?: (
- fileSystemDirectoryEntry: FileSystemDirectoryEntry
- ) => boolean;
- /**
- * Configurable setup, cleanup and `Promise` rejector usable with legacy API
- * for determining when (and reacting if) a user cancels the operation. The
- * method will be passed a reference to the internal `rejectionHandler` that
- * can, e.g., be attached to/removed from the window or called after a
- * timeout. The method should return a function that will be called when
- * either the user chooses to open a file or the `rejectionHandler` is
- * called. In the latter case, the returned function will also be passed a
- * reference to the `reject` callback for the `Promise` returned by
- * `fileOpen`, so that developers may reject the `Promise` when desired at
- * that time.
- * Example rejector:
- *
- * const file = await directoryOpen({
- * legacySetup: (rejectionHandler) => {
- * const timeoutId = setTimeout(rejectionHandler, 10_000);
- * return (reject) => {
- * clearTimeout(timeoutId);
- * if (reject) {
- * reject('My error message here.');
- * }
- * };
- * },
- * });
- *
- * ToDo: Remove this workaround once
- * https://github.com/whatwg/html/issues/6376 is specified and supported.
- */
- legacySetup?: (
- resolve: (value: FileWithDirectoryHandle) => void,
- rejectionHandler: () => void,
- input: HTMLInputElement
- ) => (reject?: (reason?: any) => void) => void;
-}): Promise;
-
-/**
- * Whether the File System Access API is supported.
- */
-export const supported: boolean;
-
-export function imageToBlob(img: HTMLImageElement): Promise;
-
-export interface FileWithHandle extends File {
- handle?: FileSystemHandle;
-}
-
-export interface FileWithDirectoryHandle extends File {
- directoryHandle?: FileSystemHandle;
-}
-
-// The following typings implement the relevant parts of the File System Access
-// API. This can be removed once the specification reaches the Candidate phase
-// and is implemented as part of microsoft/TSJS-lib-generator.
-
-export interface FileSystemHandlePermissionDescriptor {
- mode?: 'read' | 'readwrite';
-}
-
-export interface FileSystemHandle {
- readonly kind: 'file' | 'directory';
- readonly name: string;
-
- isSameEntry: (other: FileSystemHandle) => Promise;
-
- queryPermission: (
- descriptor?: FileSystemHandlePermissionDescriptor
- ) => Promise;
- requestPermission: (
- descriptor?: FileSystemHandlePermissionDescriptor
- ) => Promise;
-}
diff --git a/libs/components/board-state/src/data/browser-fs-access/index.js b/libs/components/board-state/src/data/browser-fs-access/index.js
deleted file mode 100644
index a2f2506fa..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/index.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-/**
- * @module browser-fs-access
- */
-export { fileOpen } from './file-open.js';
-export { directoryOpen } from './directory-open.js';
-export { fileSave } from './file-save.js';
-export { default as supported } from './supported.js';
diff --git a/libs/components/board-state/src/data/browser-fs-access/legacy/directory-open.js b/libs/components/board-state/src/data/browser-fs-access/legacy/directory-open.js
deleted file mode 100644
index c751c654e..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/legacy/directory-open.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/* eslint-disable */
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-/**
- * Opens a directory from disk using the legacy
- * ` ` method.
- * @type { typeof import("../../index").directoryOpen }
- */
-export default async (options = [{}]) => {
- if (!Array.isArray(options)) {
- options = [options];
- }
- options[0].recursive = options[0].recursive || false;
- return new Promise((resolve, reject) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.webkitdirectory = true;
-
- const _reject = () => cleanupListenersAndMaybeReject(reject);
- const _resolve = value => {
- if (typeof cleanupListenersAndMaybeReject === 'function') {
- cleanupListenersAndMaybeReject();
- }
- resolve(value);
- };
- // ToDo: Remove this workaround once
- // https://github.com/whatwg/html/issues/6376 is specified and supported.
- const cleanupListenersAndMaybeReject =
- options[0].legacySetup &&
- options[0].legacySetup(_resolve, _reject, input);
-
- input.addEventListener('change', () => {
- let files = Array.from(input.files);
- if (!options[0].recursive) {
- files = files.filter(file => {
- return file.webkitRelativePath.split('/').length === 2;
- });
- } else if (options[0].recursive && options[0].skipDirectory) {
- files = files.filter(file => {
- const directoriesName = file.webkitRelativePath.split('/');
- return directoriesName.every(
- directoryName =>
- !options[0].skipDirectory({
- name: directoryName,
- kind: 'directory',
- })
- );
- });
- }
-
- _resolve(files);
- });
-
- input.click();
- });
-};
diff --git a/libs/components/board-state/src/data/browser-fs-access/legacy/file-open.js b/libs/components/board-state/src/data/browser-fs-access/legacy/file-open.js
deleted file mode 100644
index 325f9484f..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/legacy/file-open.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-disable */
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-/**
- * Opens a file from disk using the legacy ` ` method.
- * @type { typeof import("../../index").fileOpen }
- */
-export default async (options = [{}]) => {
- if (!Array.isArray(options)) {
- options = [options];
- }
- return new Promise((resolve, reject) => {
- const input = document.createElement('input');
- input.type = 'file';
- const accept = [
- ...options.map(option => option.mimeTypes || []).join(),
- options.map(option => option.extensions || []).join(),
- ].join();
- input.multiple = options[0].multiple || false;
- // Empty string allows everything.
- input.accept = accept || '';
- const _reject = () => cleanupListenersAndMaybeReject(reject);
- const _resolve = value => {
- if (typeof cleanupListenersAndMaybeReject === 'function') {
- cleanupListenersAndMaybeReject();
- }
- resolve(value);
- };
- // ToDo: Remove this workaround once
- // https://github.com/whatwg/html/issues/6376 is specified and supported.
- const cleanupListenersAndMaybeReject =
- options[0].legacySetup &&
- options[0].legacySetup(_resolve, _reject, input);
- input.addEventListener('change', () => {
- _resolve(input.multiple ? Array.from(input.files) : input.files[0]);
- });
-
- input.click();
- });
-};
diff --git a/libs/components/board-state/src/data/browser-fs-access/legacy/file-save.js b/libs/components/board-state/src/data/browser-fs-access/legacy/file-save.js
deleted file mode 100644
index 639841f70..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/legacy/file-save.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/* eslint-disable */
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-/**
- * Saves a file to disk using the legacy `` method.
- * @type { typeof import("../../index").fileSave }
- */
-export default async (blobOrResponse, options = {}) => {
- if (Array.isArray(options)) {
- options = options[0];
- }
- const a = document.createElement('a');
- let data = blobOrResponse;
- // Handle the case where input is a `ReadableStream`.
- if ('body' in blobOrResponse) {
- data = await streamToBlob(
- blobOrResponse.body,
- blobOrResponse.headers.get('content-type')
- );
- }
- a.download = options.fileName || 'Untitled';
- a.href = URL.createObjectURL(data);
-
- const _reject = () => cleanupListenersAndMaybeReject(reject);
- const _resolve = () => {
- if (typeof cleanupListenersAndMaybeReject === 'function') {
- cleanupListenersAndMaybeReject();
- }
- };
- // ToDo: Remove this workaround once
- // https://github.com/whatwg/html/issues/6376 is specified and supported.
- const cleanupListenersAndMaybeReject =
- options.legacySetup && options.legacySetup(_resolve, _reject, a);
-
- a.addEventListener('click', () => {
- // `setTimeout()` due to
- // https://github.com/LLK/scratch-gui/issues/1783#issuecomment-426286393
- setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
- _resolve(null);
- });
- a.click();
- return null;
-};
-
-/**
- * Converts a passed `ReadableStream` to a `Blob`.
- * @param {ReadableStream} stream
- * @param {string} type
- * @returns {Promise}
- */
-async function streamToBlob(stream, type) {
- const reader = stream.getReader();
- const pumpedStream = new ReadableStream({
- start(controller) {
- return pump();
- /**
- * Recursively pumps data chunks out of the `ReadableStream`.
- * @type { () => Promise }
- */
- async function pump() {
- return reader.read().then(({ done, value }) => {
- if (done) {
- controller.close();
- return;
- }
- controller.enqueue(value);
- return pump();
- });
- }
- },
- });
-
- const res = new Response(pumpedStream);
- reader.releaseLock();
- return new Blob([await res.blob()], { type });
-}
diff --git a/libs/components/board-state/src/data/browser-fs-access/supported.js b/libs/components/board-state/src/data/browser-fs-access/supported.js
deleted file mode 100644
index 06a34ebdd..000000000
--- a/libs/components/board-state/src/data/browser-fs-access/supported.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* eslint-disable */
-/**
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
-
-/**
- * Returns whether the File System Access API is supported and usable in the
- * current context (for example cross-origin iframes).
- * @returns {boolean} Returns `true` if the File System Access API is supported and usable, else returns `false`.
- */
-const supported = (() => {
- // When running in an SSR environment return `false`.
- if (typeof self === 'undefined') {
- return false;
- }
- // ToDo: Remove this check once Permissions Policy integration
- // has happened, tracked in
- // https://github.com/WICG/file-system-access/issues/245.
- if ('top' in self && self !== top) {
- try {
- // This will succeed on same-origin iframes,
- // but fail on cross-origin iframes.
- top.location + '';
- } catch {
- return false;
- }
- } else if ('showOpenFilePicker' in self) {
- return 'showOpenFilePicker';
- }
- return false;
-})();
-
-export default supported;
diff --git a/libs/components/board-state/src/data/filesystem.spec.ts b/libs/components/board-state/src/data/filesystem.spec.ts
deleted file mode 100644
index c91fdfff9..000000000
--- a/libs/components/board-state/src/data/filesystem.spec.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-describe('when saving data to the file system', () => {
- it.todo('saves a new file in the filesystem');
- it.todo('saves a new file in the filesystem');
-});
-
-describe('when opening files from file system', () => {
- it.todo('opens a file and loads it into the document');
- it.todo('opens an older file, migrates it, and loads it into the document');
- it.todo(
- 'opens a corrupt file, tries to fix it, and fails without crashing'
- );
-});
diff --git a/libs/components/board-state/src/data/filesystem.ts b/libs/components/board-state/src/data/filesystem.ts
deleted file mode 100644
index 9825b1e59..000000000
--- a/libs/components/board-state/src/data/filesystem.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-import type { TDDocument, TDFile } from '@toeverything/components/board-types';
-import {
- IMAGE_EXTENSIONS,
- VIDEO_EXTENSIONS,
-} from '@toeverything/components/board-types';
-import { get as getFromIdb, set as setToIdb } from 'idb-keyval';
-import type { FileSystemHandle } from './browser-fs-access';
-
-const options = { mode: 'readwrite' as const };
-
-const checkPermissions = async (handle: FileSystemHandle) => {
- return (
- (await handle.queryPermission(options)) === 'granted' ||
- (await handle.requestPermission(options)) === 'granted'
- );
-};
-
-export async function loadFileHandle() {
- if (typeof Window === 'undefined' || !('_location' in Window)) return;
- const fileHandle = await getFromIdb(
- `Tldraw_file_handle_${window.location.origin}`
- );
- if (!fileHandle) return null;
- return fileHandle;
-}
-
-export async function saveFileHandle(fileHandle: FileSystemHandle | null) {
- return setToIdb(`Tldraw_file_handle_${window.location.origin}`, fileHandle);
-}
-
-export async function saveToFileSystem(
- document: TDDocument,
- fileHandle: FileSystemHandle | null
-) {
- // Create the saved file data
- const file: TDFile = {
- name: document.name || 'New Document',
- fileHandle: fileHandle ?? null,
- document,
- assets: {},
- };
-
- // Serialize to JSON
- const json = JSON.stringify(file, null, 2);
-
- // Create blob
- const blob = new Blob([json], {
- type: 'application/vnd.Tldraw+json',
- });
-
- if (fileHandle) {
- const hasPermissions = await checkPermissions(fileHandle);
- if (!hasPermissions) return null;
- }
-
- // Save to file system
- // @ts-ignore
- const browser_fs = await import('./browser-fs-access');
- const fileSave = browser_fs.fileSave;
- const newFileHandle = await fileSave(
- blob,
- {
- fileName: `${file.name}.tldr`,
- description: 'Tldraw File',
- extensions: [`.tldr`],
- },
- fileHandle
- );
-
- await saveFileHandle(newFileHandle);
-
- // Return true
- return newFileHandle;
-}
-
-export async function openFromFileSystem(): Promise {
- // Get the blob
- // @ts-ignore
- const browser_fs = await import('./browser-fs-access');
- const fileOpen = browser_fs.fileOpen;
- const blob = await fileOpen({
- description: 'Tldraw File',
- extensions: [`.tldr`],
- multiple: false,
- });
-
- if (!blob) return null;
-
- // Get JSON from blob
- const json: string = await new Promise(resolve => {
- const reader = new FileReader();
- reader.onloadend = () => {
- if (reader.readyState === FileReader.DONE) {
- resolve(reader.result as string);
- }
- };
- reader.readAsText(blob, 'utf8');
- });
-
- // Parse
- const file: TDFile = JSON.parse(json);
-
- const fileHandle = blob.handle ?? null;
-
- await saveFileHandle(fileHandle);
-
- return {
- fileHandle,
- document: file.document,
- };
-}
-
-export async function openAssetFromFileSystem() {
- // @ts-ignore
- const browser_fs = await import('./browser-fs-access');
- const fileOpen = browser_fs.fileOpen;
- return fileOpen({
- description: 'Image or Video',
- extensions: [...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS],
- multiple: false,
- });
-}
-
-export function fileToBase64(file: Blob): Promise {
- return new Promise((resolve, reject) => {
- if (file) {
- const reader = new FileReader();
- reader.readAsDataURL(file);
- reader.onload = () => resolve(reader.result);
- reader.onerror = error => reject(error);
- reader.onabort = error => reject(error);
- }
- });
-}
-
-export function fileToText(file: Blob): Promise {
- return new Promise((resolve, reject) => {
- if (file) {
- const reader = new FileReader();
- reader.readAsText(file);
- reader.onload = () => resolve(reader.result);
- reader.onerror = error => reject(error);
- reader.onabort = error => reject(error);
- }
- });
-}
-
-export function getImageSizeFromSrc(src: string): Promise {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.onload = () => resolve([img.width, img.height]);
- img.onerror = () => reject(new Error('Could not get image size'));
- img.src = src;
- });
-}
-
-export function getVideoSizeFromSrc(src: string): Promise {
- return new Promise((resolve, reject) => {
- const video = document.createElement('video');
- video.onloadedmetadata = () =>
- resolve([video.videoWidth, video.videoHeight]);
- video.onerror = () => reject(new Error('Could not get video size'));
- video.src = src;
- });
-}
diff --git a/libs/components/board-state/src/data/index.ts b/libs/components/board-state/src/data/index.ts
deleted file mode 100644
index a00fd2a70..000000000
--- a/libs/components/board-state/src/data/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './browser-fs-access';
-export * from './filesystem';
-export * from './migrate';
diff --git a/libs/components/board-state/src/data/migrate.spec.ts b/libs/components/board-state/src/data/migrate.spec.ts
deleted file mode 100644
index 30e0612c5..000000000
--- a/libs/components/board-state/src/data/migrate.spec.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { TldrawApp } from '~state';
-import oldDoc from '~test/documents/old-doc';
-import oldDoc2 from '~test/documents/old-doc-2';
-import type { TDDocument } from '~types';
-
-describe('When migrating bindings', () => {
- it('migrates a document without a version', () => {
- new TldrawApp().loadDocument(oldDoc as unknown as TDDocument);
- });
-
- it('migrates a document with an older version', () => {
- const app = new TldrawApp().loadDocument(
- oldDoc2 as unknown as TDDocument
- );
- expect(
- app.getShape('d7ab0a49-3cb3-43ae-3d83-f5cf2f4a510a').style.color
- ).toBe('black');
- });
-});
diff --git a/libs/components/board-state/src/data/migrate.ts b/libs/components/board-state/src/data/migrate.ts
deleted file mode 100644
index d4c14a5ed..000000000
--- a/libs/components/board-state/src/data/migrate.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-import {
- Decoration,
- TDDocument,
- TDShapeType,
-} from '@toeverything/components/board-types';
-
-export function migrate(document: TDDocument, newVersion: number): TDDocument {
- const { version = 0 } = document;
-
- if (!('assets' in document)) {
- document.assets = {};
- }
-
- // Remove unused assets when loading a document
- const assetIdsInUse = new Set();
-
- Object.values(document.pages).forEach(page =>
- Object.values(page.shapes).forEach(shape => {
- const { parentId, children, assetId } = shape;
-
- if (assetId) {
- assetIdsInUse.add(assetId);
- }
-
- // Fix missing parent bug
- if (parentId !== page.id && !page.shapes[parentId]) {
- console.warn('Encountered a shape with a missing parent!');
- shape.parentId = page.id;
- }
-
- if (shape.type === TDShapeType.Group && children) {
- children.forEach(childId => {
- if (!page.shapes[childId]) {
- console.warn(
- 'Encountered a parent with a missing child!',
- shape.id,
- childId
- );
- children?.splice(children.indexOf(childId), 1);
- }
- });
-
- // TODO: Remove the shape if it has no children
- }
- })
- );
-
- Object.keys(document.assets).forEach(assetId => {
- if (!assetIdsInUse.has(assetId)) {
- delete document.assets[assetId];
- }
- });
-
- if (version === newVersion) return document;
-
- // if (version < 14) {
- // Object.values(document.pages).forEach(page => {
- // Object.values(page.shapes)
- // // .filter(shape => shape.type === TDShapeType.Text)
- // .forEach(
- // shape =>
- // (shape as TextShape).style.font === FontStyle.Script
- // );
- // });
- // }
-
- // Lowercase styles, move binding meta to binding
- if (version <= 13) {
- Object.values(document.pages).forEach(page => {
- Object.values(page.bindings).forEach(binding => {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- Object.assign(binding, (binding as any).meta);
- });
-
- Object.values(page.shapes).forEach(shape => {
- Object.entries(shape.style).forEach(([id, style]) => {
- if (typeof style === 'string') {
- // @ts-ignore
- shape.style[id] = style.toLowerCase();
- }
- });
-
- if (shape.type === TDShapeType.Arrow) {
- if (shape.decorations) {
- Object.entries(shape.decorations).forEach(
- ([id, decoration]) => {
- if ((decoration as unknown) === 'Arrow') {
- shape.decorations = {
- ...shape.decorations,
- [id]: Decoration.Arrow,
- };
- }
- }
- );
- }
- }
- });
- });
- }
-
- // Add document name and file system handle
- if (version <= 13.1) {
- document.name = 'New Document';
- }
-
- if (version < 15) {
- document.assets = {};
- }
-
- Object.values(document.pages).forEach(page => {
- Object.values(page.shapes).forEach(shape => {
- if (version < 15.2) {
- if (
- shape.type === TDShapeType.Image ||
- shape.type === TDShapeType.Video
- ) {
- shape.style.isFilled = true;
- }
- }
-
- if (version < 15.3) {
- if (
- shape.type === TDShapeType.Rectangle ||
- shape.type === TDShapeType.Triangle ||
- shape.type === TDShapeType.Ellipse ||
- shape.type === TDShapeType.Arrow
- ) {
- shape.label = (shape as any).text || '';
- shape.labelPoint = [0.5, 0.5];
- }
- }
- });
- });
-
- // Cleanup
- Object.values(document.pageStates).forEach(pageState => {
- pageState.selectedIds = pageState.selectedIds.filter(id => {
- return document.pages[pageState.id].shapes[id] !== undefined;
- });
- pageState.bindingId = undefined;
- pageState.editingId = undefined;
- pageState.hoveredId = undefined;
- pageState.pointedId = undefined;
- });
-
- document.version = newVersion;
-
- return document;
-}
diff --git a/libs/components/board-state/src/idb-clipboard.ts b/libs/components/board-state/src/idb-clipboard.ts
deleted file mode 100644
index 15af99f99..000000000
--- a/libs/components/board-state/src/idb-clipboard.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { del, get, set } from 'idb-keyval';
-
-// Used for clipboard
-
-const ID = 'tldraw_clipboard';
-
-export async function getClipboard(): Promise {
- return get(ID);
-}
-
-export async function setClipboard(item: string): Promise {
- return set(ID, item);
-}
-
-export function clearClipboard(): Promise {
- return del(ID);
-}
diff --git a/libs/components/board-state/src/index.ts b/libs/components/board-state/src/index.ts
deleted file mode 100644
index e43f91211..000000000
--- a/libs/components/board-state/src/index.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export { deepCopy } from './manager/deep-copy';
-export { TLDR } from './tldr';
-export { TldrawApp } from './tldraw-app';
-export type { TldrawAppCtorProps } from './tldraw-app';
-export { BaseTool, Status as BaseToolStatus } from './types/tool';
diff --git a/libs/components/board-state/src/manager/deep-copy.ts b/libs/components/board-state/src/manager/deep-copy.ts
deleted file mode 100644
index 0c6f8186e..000000000
--- a/libs/components/board-state/src/manager/deep-copy.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- * Deep copy function for TypeScript.
- * @param T Generic type of target/copied value.
- * @param target Target value to be copied.
- * @see Source project, ts-deeply https://github.com/ykdr2017/ts-deepcopy
- * @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
- */
-export function deepCopy(target: T): T {
- if (target === null) {
- return target;
- }
- if (target instanceof Date) {
- return new Date(target.getTime()) as any;
- }
-
- // First part is for array and second part is for Realm.Collection
- // if (target instanceof Array || typeof (target as any).type === 'string') {
- if (typeof target === 'object') {
- if (typeof target[Symbol.iterator as keyof T] === 'function') {
- const cp = [] as any[];
- if ((target as any as any[]).length > 0) {
- for (const arrayMember of target as any as any[]) {
- cp.push(deepCopy(arrayMember));
- }
- }
- return cp as any as T;
- } else {
- const targetKeys = Object.keys(target);
- const cp = {} as T;
- if (targetKeys.length > 0) {
- for (const key of targetKeys) {
- cp[key as keyof T] = deepCopy(target[key as keyof T]);
- }
- }
- return cp;
- }
- }
-
- // Means that object is atomic
- return target;
-}
diff --git a/libs/components/board-state/src/manager/state-manager.ts b/libs/components/board-state/src/manager/state-manager.ts
deleted file mode 100644
index 908e9134a..000000000
--- a/libs/components/board-state/src/manager/state-manager.ts
+++ /dev/null
@@ -1,450 +0,0 @@
-import { Utils } from '@tldraw/core';
-import type { Command, Patch } from '@toeverything/components/board-types';
-import * as idb from 'idb-keyval';
-import create, { UseBoundStore } from 'zustand';
-import createVanilla, { StoreApi } from 'zustand/vanilla';
-import { deepCopy } from './deep-copy';
-
-export class StateManager> {
- /**
- * An ID used to persist state in indexdb.
- */
- protected idb_id?: string;
-
- /**
- * The initial state.
- */
- private initial_state: T;
-
- /**
- * A zustand store that also holds the state.
- */
- private store: StoreApi;
-
- /**
- * The index of the current command.
- */
- protected pointer = -1;
-
- /**
- * The current state.
- */
- private _state: T;
-
- /**
- * The state manager's current status, with regard to restoring persisted state.
- */
- private _status: 'loading' | 'ready' = 'loading';
-
- /**
- * A stack of commands used for history (undo and redo).
- */
- protected stack: Command[] = [];
-
- /**
- * A snapshot of the current state.
- */
- protected _snapshot: T;
-
- /* eslint-disable @typescript-eslint/naming-convention */
- /**
- * A React hook for accessing the zustand store.
- */
- public readonly useStore: UseBoundStore;
- /* eslint-enable @typescript-eslint/naming-convention */
-
- /**
- * A promise that will resolve when the state manager has loaded any peristed state.
- */
- public ready: Promise<'none' | 'restored' | 'migrated'>;
-
- public isPaused = false;
-
- constructor(
- initialState: T,
- id?: string,
- version?: number,
- update?: (prev: T, next: T, prevVersion: number) => T
- ) {
- this.idb_id = id;
- this._state = deepCopy(initialState);
- this._snapshot = deepCopy(initialState);
- this.initial_state = deepCopy(initialState);
- this.store = createVanilla(() => this._state);
- this.useStore = create(this.store);
-
- this.ready = new Promise<'none' | 'restored' | 'migrated'>(resolve => {
- let message: 'none' | 'restored' | 'migrated' = 'none';
-
- if (this.idb_id) {
- message = 'restored';
-
- idb.get(this.idb_id)
- .then(async saved => {
- if (saved) {
- let next = saved;
-
- if (version) {
- const savedVersion = await idb.get(
- id + '_version'
- );
-
- if (savedVersion && savedVersion < version) {
- next = update
- ? update(
- saved,
- initialState,
- savedVersion
- )
- : initialState;
-
- message = 'migrated';
- }
- }
-
- await idb.set(id + '_version', version || -1);
-
- // why is this necessary? but it is...
- const prevEmpty =
- this._state['appState'].isEmptyCanvas;
-
- next = this.migrate(next);
-
- this._state = deepCopy(next);
- this._snapshot = deepCopy(next);
-
- this._state['appState'].isEmptyCanvas = prevEmpty;
- this.store.setState(this._state, true);
- } else {
- await idb.set(id + '_version', version || -1);
- }
- this._status = 'ready';
- resolve(message);
- })
- .catch(e => console.error(e));
- } else {
- // We need to wait for any override to `onReady` to take effect.
- this._status = 'ready';
- resolve(message);
- }
- }).then(message => {
- if (this.on_ready) this.on_ready(message);
- return message;
- });
- }
-
- /**
- * Save the current state to indexdb.
- */
- protected persist = (id?: string): void | Promise => {
- if (this._status !== 'ready') return;
-
- if (this.onPersist) {
- this.onPersist(this._state, id);
- }
-
- if (this.idb_id) {
- return idb
- .set(this.idb_id, this._state)
- .catch(e => console.error(e));
- }
- };
-
- /**
- * Apply a patch to the current state.
- * This does not effect the undo/redo stack.
- * This does not persist the state.
- * @param patch The patch to apply.
- * @param id (optional) An id for the patch.
- */
- private apply_patch = (patch: Patch, id?: string) => {
- const prev = this._state;
- const next = Utils.deepMerge(this._state, patch as any);
- const final = this.cleanup(next, prev, patch, id);
- if (this.on_state_will_change) {
- this.on_state_will_change(final, id);
- }
- this._state = final;
- this.store.setState(this._state, true);
- if (this.on_state_did_change) {
- this.on_state_did_change(this._state, id);
- }
- return this;
- };
-
- // Internal API ---------------------------------
-
- protected migrate = (next: T): T => {
- return next;
- };
-
- /**
- * Perform any last changes to the state before updating.
- * Override this on your extending class.
- * @param nextState The next state.
- * @param prevState The previous state.
- * @param patch The patch that was just applied.
- * @param id (optional) An id for the just-applied patch.
- * @returns The final new state to apply.
- */
- protected cleanup = (
- nextState: T,
- prevState: T,
- patch: Patch,
- id?: string
- ): T => nextState;
-
- /**
- * A life-cycle method called when the state is about to change.
- * @param state The next state.
- * @param id An id for the change.
- */
- protected on_state_will_change?: (state: T, id?: string) => void;
-
- /**
- * A life-cycle method called when the state has changed.
- * @param state The next state.
- * @param id An id for the change.
- */
- protected on_state_did_change?: (state: T, id?: string) => void;
-
- /**
- * Apply a patch to the current state.
- * This does not effect the undo/redo stack.
- * This does not persist the state.
- * @param patch The patch to apply.
- * @param id (optional) An id for this patch.
- */
- patchState = (patch: Patch, id?: string): this => {
- this.apply_patch(patch, id);
- if (this.onPatch) {
- this.onPatch(this._state, id);
- }
- return this;
- };
-
- /**
- * Replace the current state.
- * This does not effect the undo/redo stack.
- * This does not persist the state.
- * @param state The new state.
- * @param id An id for this change.
- */
- protected replace_state = (state: T, id?: string): this => {
- const final = this.cleanup(state, this._state, state, id);
- if (this.on_state_will_change) {
- this.on_state_will_change(final, 'replace');
- }
- this._state = final;
- this.store.setState(this._state, true);
- if (this.on_state_did_change) {
- this.on_state_did_change(this._state, 'replace');
- }
- return this;
- };
-
- /**
- * Update the state using a Command.
- * This effects the undo/redo stack.
- * This persists the state.
- * @param command The command to apply and add to the undo/redo stack.
- * @param id (optional) An id for this command.
- */
- protected set_state = (command: Command, id = command.id) => {
- if (this.pointer < this.stack.length - 1) {
- this.stack = this.stack.slice(0, this.pointer + 1);
- }
- this.stack.push({ ...command, id });
- this.pointer = this.stack.length - 1;
- this.apply_patch(command.after, id);
- if (this.onCommand) this.onCommand(this._state, id);
- this.persist(id);
- return this;
- };
-
- // Public API ---------------------------------
-
- public pause() {
- this.isPaused = true;
- }
-
- public resume() {
- this.isPaused = false;
- }
-
- /**
- * A callback fired when the constructor finishes loading any
- * persisted data.
- */
- protected on_ready?: (message: 'none' | 'restored' | 'migrated') => void;
-
- /**
- * A callback fired when a patch is applied.
- */
- public onPatch?: (state: T, id?: string) => void;
-
- /**
- * A callback fired when a patch is applied.
- */
- public onCommand?: (state: T, id?: string) => void;
-
- /**
- * A callback fired when the state is persisted.
- */
- public onPersist?: (state: T, id?: string) => void;
-
- /**
- * A callback fired when the state is replaced.
- */
- public onReplace?: (state: T) => void;
-
- /**
- * A callback fired when the state is reset.
- */
- public onReset?: (state: T) => void;
-
- /**
- * A callback fired when the history is reset.
- */
- public onResetHistory?: (state: T) => void;
-
- /**
- * A callback fired when a command is undone.
- */
- public onUndo?: (state: T) => void;
-
- /**
- * A callback fired when a command is redone.
- */
- public onRedo?: (state: T) => void;
-
- /**
- * Reset the state to the initial state and reset history.
- */
- public reset = () => {
- if (this.on_state_will_change) {
- this.on_state_will_change(this.initial_state, 'reset');
- }
- this._state = this.initial_state;
- this.store.setState(this._state, true);
- this.resetHistory();
- this.persist('reset');
- if (this.on_state_did_change) {
- this.on_state_did_change(this._state, 'reset');
- }
- if (this.onReset) {
- this.onReset(this._state);
- }
- return this;
- };
-
- /**
- * Force replace a new undo/redo history. It's your responsibility
- * to make sure that this is compatible with the current state!
- * @param history The new array of commands.
- * @param pointer (optional) The new pointer position.
- */
- public replaceHistory = (
- history: Command[],
- pointer = history.length - 1
- ): this => {
- this.stack = history;
- this.pointer = pointer;
- if (this.onReplace) {
- this.onReplace(this._state);
- }
- return this;
- };
-
- /**
- * Reset the history stack (without resetting the state).
- */
- public resetHistory = (): this => {
- this.stack = [];
- this.pointer = -1;
- if (this.onResetHistory) {
- this.onResetHistory(this._state);
- }
- return this;
- };
-
- /**
- * Move backward in the undo/redo stack.
- */
- public undo = (): this => {
- if (!this.isPaused) {
- if (!this.canUndo) return this;
- const command = this.stack[this.pointer];
- this.pointer--;
- this.apply_patch(command.before, `undo`);
- this.persist('undo');
- }
- if (this.onUndo) this.onUndo(this._state);
- return this;
- };
-
- /**
- * Move forward in the undo/redo stack.
- */
- public redo = (): this => {
- if (!this.isPaused) {
- if (!this.canRedo) return this;
- this.pointer++;
- const command = this.stack[this.pointer];
- this.apply_patch(command.after, 'redo');
- this.persist('undo');
- }
- if (this.onRedo) this.onRedo(this._state);
- return this;
- };
-
- /**
- * Save a snapshot of the current state, accessible at `this.snapshot`.
- */
- public setSnapshot = (): this => {
- this._snapshot = { ...this._state };
- return this;
- };
-
- /**
- * Force the zustand state to update.
- */
- public forceUpdate = () => {
- this.store.setState(this._state, true);
- };
-
- /**
- * Get whether the state manager can undo.
- */
- public get canUndo(): boolean {
- return this.pointer > -1;
- }
-
- /**
- * Get whether the state manager can redo.
- */
- public get canRedo(): boolean {
- return this.pointer < this.stack.length - 1;
- }
-
- /**
- * The current state.
- */
- public get state(): T {
- return this._state;
- }
-
- /**
- * The current status.
- */
- public get status(): string {
- return this._status;
- }
-
- /**
- * The most-recent snapshot.
- */
- protected get snapshot(): T {
- return this._snapshot;
- }
-}
diff --git a/libs/components/board-state/src/tldr.ts b/libs/components/board-state/src/tldr.ts
deleted file mode 100644
index fbd330412..000000000
--- a/libs/components/board-state/src/tldr.ts
+++ /dev/null
@@ -1,1425 +0,0 @@
-/* eslint-disable @typescript-eslint/no-non-null-assertion */
-import { TLBounds, TLPageState, TLTransformInfo, Utils } from '@tldraw/core';
-import {
- intersectRayBounds,
- intersectRayEllipse,
- intersectRayLineSegment,
-} from '@tldraw/intersect';
-import { Vec } from '@tldraw/vec';
-import type { TDShapeUtil } from '@toeverything/components/board-shapes';
-import {
- getShapeUtil,
- getTrianglePoints,
-} from '@toeverything/components/board-shapes';
-import {
- ArrowShape,
- BINDING_DISTANCE,
- ShapesWithProp,
- TDBinding,
- TDExportType,
- TDHandle,
- TDPage,
- TDShape,
- TDShapeType,
- TDSnapshot,
- TldrawCommand,
- TldrawPatch,
-} from '@toeverything/components/board-types';
-import { deepCopy } from './manager/deep-copy';
-
-const isDev = process.env['NODE_ENV'] === 'development';
-
-export class TLDR {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- static get_shape_util(type: T['type']): TDShapeUtil;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- static get_shape_util