feat: support web grid preview (#5353)

This commit is contained in:
Kilu.He 2024-05-17 18:23:29 +08:00 committed by GitHub
parent 759998cacb
commit c1e7e72154
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
241 changed files with 5570 additions and 937 deletions

View File

@ -7,7 +7,7 @@
"type": "color" "type": "color"
}, },
"100": { "100": {
"value": "#edeef2", "value": "#dadbdd",
"type": "color" "type": "color"
}, },
"200": { "200": {

View File

@ -0,0 +1,61 @@
{
"data": {
"user_profile": {
"uid": 304120109071339520,
"uuid": "cbff060a-196d-415a-aa80-759c01886466",
"email": "lu@appflowy.io",
"password": "",
"name": "Kilu",
"metadata": {
"icon_url": "🇽🇰"
},
"encryption_sign": null,
"latest_workspace_id": "9eebea03-3ed5-4298-86b2-a7f77856d48b",
"updated_at": 1715847453
},
"visiting_workspace": {
"workspace_id": "9eebea03-3ed5-4298-86b2-a7f77856d48b",
"database_storage_id": "375874be-7a4f-4b7c-8b89-1dc9a39838f4",
"owner_uid": 304120109071339520,
"owner_name": "Kilu",
"workspace_type": 0,
"workspace_name": "Kilu Works",
"created_at": "2024-03-13T07:23:10.275174Z",
"icon": "😆"
},
"workspaces": [
{
"workspace_id": "81570fa8-8be9-4b2d-9f1c-1ef4f34079a8",
"database_storage_id": "6c1f1a2c-e8d5-4bc2-917f-495bce862abb",
"owner_uid": 311828434584080384,
"owner_name": "Zack Zi Xiang Fu",
"workspace_type": 0,
"workspace_name": "My Workspace",
"created_at": "2024-04-03T13:53:18.295918Z",
"icon": ""
},
{
"workspace_id": "fcb503f9-9287-4de4-8de0-ea191e680968",
"database_storage_id": "ae1b82a5-2b93-45c7-901a-f9357c544534",
"owner_uid": 276169796100296704,
"owner_name": "Annie Anqi Wang",
"workspace_type": 0,
"workspace_name": "AppFlowy Test",
"created_at": "2023-12-27T04:18:36.372013Z",
"icon": ""
},
{
"workspace_id": "9eebea03-3ed5-4298-86b2-a7f77856d48b",
"database_storage_id": "375874be-7a4f-4b7c-8b89-1dc9a39838f4",
"owner_uid": 304120109071339520,
"owner_name": "Kilu",
"workspace_type": 0,
"workspace_name": "Kilu Works",
"created_at": "2024-03-13T07:23:10.275174Z",
"icon": "😆"
}
]
},
"code": 0,
"message": "Operation completed successfully."
}

View File

@ -37,6 +37,7 @@ Cypress.Commands.add('mockAPI', () => {
cy.intercept('POST', '/gotrue/token?grant_type=refresh_token', json).as('refreshToken'); cy.intercept('POST', '/gotrue/token?grant_type=refresh_token', json).as('refreshToken');
}); });
cy.intercept('GET', '/api/user/profile', { fixture: 'user' }).as('getUserProfile'); cy.intercept('GET', '/api/user/profile', { fixture: 'user' }).as('getUserProfile');
cy.intercept('GET', '/api/user/workspace', { fixture: 'user_workspace' }).as('getUserWorkspace');
}); });
// Example use: // Example use:

View File

@ -3,7 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/appflowy.svg" /> <link rel="icon" type="image/svg+xml" href="/appflowy.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport"
content="width=device-width,height=device-height,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
>
<title>AppFlowy</title> <title>AppFlowy</title>
</head> </head>
<body id="body"> <body id="body">

View File

@ -22,12 +22,13 @@
"test:unit": "jest" "test:unit": "jest"
}, },
"dependencies": { "dependencies": {
"@appflowyinc/client-api-wasm": "0.0.2-alpha.2", "@appflowyinc/client-api-wasm": "0.0.3",
"@atlaskit/primitives": "^5.5.3", "@atlaskit/primitives": "^5.5.3",
"@emoji-mart/data": "^1.1.2", "@emoji-mart/data": "^1.1.2",
"@emoji-mart/react": "^1.1.1", "@emoji-mart/react": "^1.1.1",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@jest/globals": "^29.7.0",
"@mui/icons-material": "^5.11.11", "@mui/icons-material": "^5.11.11",
"@mui/material": "6.0.0-alpha.2", "@mui/material": "6.0.0-alpha.2",
"@mui/x-date-pickers-pro": "^6.18.2", "@mui/x-date-pickers-pro": "^6.18.2",
@ -35,9 +36,10 @@
"@slate-yjs/core": "^1.0.2", "@slate-yjs/core": "^1.0.2",
"@tauri-apps/api": "^1.5.3", "@tauri-apps/api": "^1.5.3",
"@types/react-swipeable-views": "^0.13.4", "@types/react-swipeable-views": "^0.13.4",
"async-retry": "^1.3.3",
"axios": "^1.6.8", "axios": "^1.6.8",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"dexie": "^4.0.1", "decimal.js": "^10.4.3",
"emoji-mart": "^5.5.2", "emoji-mart": "^5.5.2",
"emoji-regex": "^10.2.1", "emoji-regex": "^10.2.1",
"events": "^3.3.0", "events": "^3.3.0",
@ -51,6 +53,7 @@
"katex": "^0.16.7", "katex": "^0.16.7",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nanoid": "^4.0.0", "nanoid": "^4.0.0",
"numeral": "^2.0.6",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"protoc-gen-ts": "0.8.7", "protoc-gen-ts": "0.8.7",
"quill": "^1.3.7", "quill": "^1.3.7",
@ -66,6 +69,7 @@
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-i18next": "^14.1.0", "react-i18next": "^14.1.0",
"react-katex": "^3.0.1", "react-katex": "^3.0.1",
"react-measure": "^2.5.2",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-swipeable-views": "^0.14.0", "react-swipeable-views": "^0.14.0",
@ -98,6 +102,7 @@
"@types/katex": "^0.16.0", "@types/katex": "^0.16.0",
"@types/lodash-es": "^4.17.11", "@types/lodash-es": "^4.17.11",
"@types/node": "^20.11.30", "@types/node": "^20.11.30",
"@types/numeral": "^2.0.5",
"@types/prismjs": "^1.26.0", "@types/prismjs": "^1.26.0",
"@types/quill": "^2.0.10", "@types/quill": "^2.0.10",
"@types/react": "^18.2.66", "@types/react": "^18.2.66",
@ -107,6 +112,7 @@
"@types/react-datepicker": "^4.19.3", "@types/react-datepicker": "^4.19.3",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"@types/react-katex": "^3.0.0", "@types/react-katex": "^3.0.0",
"@types/react-measure": "^2.0.12",
"@types/react-transition-group": "^4.4.6", "@types/react-transition-group": "^4.4.6",
"@types/react-window": "^1.8.8", "@types/react-window": "^1.8.8",
"@types/utf8": "^3.0.1", "@types/utf8": "^3.0.1",

View File

@ -6,8 +6,8 @@ settings:
dependencies: dependencies:
'@appflowyinc/client-api-wasm': '@appflowyinc/client-api-wasm':
specifier: 0.0.2-alpha.2 specifier: 0.0.3
version: 0.0.2-alpha.2 version: 0.0.3
'@atlaskit/primitives': '@atlaskit/primitives':
specifier: ^5.5.3 specifier: ^5.5.3
version: 5.5.3(@types/react@18.2.66)(react@18.2.0) version: 5.5.3(@types/react@18.2.66)(react@18.2.0)
@ -23,6 +23,9 @@ dependencies:
'@emotion/styled': '@emotion/styled':
specifier: ^11.10.6 specifier: ^11.10.6
version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.2.66)(react@18.2.0) version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.2.66)(react@18.2.0)
'@jest/globals':
specifier: ^29.7.0
version: 29.7.0
'@mui/icons-material': '@mui/icons-material':
specifier: ^5.11.11 specifier: ^5.11.11
version: 5.11.11(@mui/material@6.0.0-alpha.2)(@types/react@18.2.66)(react@18.2.0) version: 5.11.11(@mui/material@6.0.0-alpha.2)(@types/react@18.2.66)(react@18.2.0)
@ -44,15 +47,18 @@ dependencies:
'@types/react-swipeable-views': '@types/react-swipeable-views':
specifier: ^0.13.4 specifier: ^0.13.4
version: 0.13.4 version: 0.13.4
async-retry:
specifier: ^1.3.3
version: 1.3.3
axios: axios:
specifier: ^1.6.8 specifier: ^1.6.8
version: 1.6.8 version: 1.6.8
dayjs: dayjs:
specifier: ^1.11.9 specifier: ^1.11.9
version: 1.11.9 version: 1.11.9
dexie: decimal.js:
specifier: ^4.0.1 specifier: ^10.4.3
version: 4.0.1 version: 10.4.3
emoji-mart: emoji-mart:
specifier: ^5.5.2 specifier: ^5.5.2
version: 5.5.2 version: 5.5.2
@ -92,6 +98,9 @@ dependencies:
nanoid: nanoid:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0 version: 4.0.0
numeral:
specifier: ^2.0.6
version: 2.0.6
prismjs: prismjs:
specifier: ^1.29.0 specifier: ^1.29.0
version: 1.29.0 version: 1.29.0
@ -137,6 +146,9 @@ dependencies:
react-katex: react-katex:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1(prop-types@15.8.1)(react@18.2.0) version: 3.0.1(prop-types@15.8.1)(react@18.2.0)
react-measure:
specifier: ^2.5.2
version: 2.5.2(react-dom@18.2.0)(react@18.2.0)
react-redux: react-redux:
specifier: ^8.0.5 specifier: ^8.0.5
version: 8.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) version: 8.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
@ -229,6 +241,9 @@ devDependencies:
'@types/node': '@types/node':
specifier: ^20.11.30 specifier: ^20.11.30
version: 20.11.30 version: 20.11.30
'@types/numeral':
specifier: ^2.0.5
version: 2.0.5
'@types/prismjs': '@types/prismjs':
specifier: ^1.26.0 specifier: ^1.26.0
version: 1.26.0 version: 1.26.0
@ -256,6 +271,9 @@ devDependencies:
'@types/react-katex': '@types/react-katex':
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0 version: 3.0.0
'@types/react-measure':
specifier: ^2.0.12
version: 2.0.12
'@types/react-transition-group': '@types/react-transition-group':
specifier: ^4.4.6 specifier: ^4.4.6
version: 4.4.6 version: 4.4.6
@ -376,8 +394,8 @@ packages:
'@jridgewell/gen-mapping': 0.3.5 '@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25 '@jridgewell/trace-mapping': 0.3.25
/@appflowyinc/client-api-wasm@0.0.2-alpha.2: /@appflowyinc/client-api-wasm@0.0.3:
resolution: {integrity: sha512-BcRK06zHHJdaGNYohYxGaR2xPfQ1RwU48jMzdMZDf2HXVLU2WWQ6cYfuM4lrsK+O3QEfJdeEL2fntnQDaaeQng==} resolution: {integrity: sha512-ARjLhiDZ8MiZ9egWDbAX9VAdXXS30av+InCPLrS/iqCMYrhuuU9rxS9jQeNEB7jucFrj158gBRusimFN7P/lyw==}
dev: false dev: false
/@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0): /@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0):
@ -2677,6 +2695,10 @@ packages:
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
/@types/numeral@2.0.5:
resolution: {integrity: sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw==}
dev: true
/@types/parse-json@4.0.2: /@types/parse-json@4.0.2:
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
dev: false dev: false
@ -2737,6 +2759,12 @@ packages:
'@types/react': 18.2.66 '@types/react': 18.2.66
dev: true dev: true
/@types/react-measure@2.0.12:
resolution: {integrity: sha512-Y6V11CH6bU7RhqrIdENPwEUZlPXhfXNGylMNnGwq5TAEs2wDoBA3kSVVM/EQ8u72sz5r9ja+7W8M8PIVcS841Q==}
dependencies:
'@types/react': 18.2.66
dev: true
/@types/react-redux@7.1.33: /@types/react-redux@7.1.33:
resolution: {integrity: sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==} resolution: {integrity: sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==}
dependencies: dependencies:
@ -3234,6 +3262,12 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/async-retry@1.3.3:
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
dependencies:
retry: 0.13.1
dev: false
/async@3.2.5: /async@3.2.5:
resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==}
dev: true dev: true
@ -4015,7 +4049,6 @@ packages:
/decimal.js@10.4.3: /decimal.js@10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
dev: true
/dedent@1.5.1: /dedent@1.5.1:
resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==}
@ -4101,10 +4134,6 @@ packages:
minimist: 1.2.8 minimist: 1.2.8
dev: true dev: true
/dexie@4.0.1:
resolution: {integrity: sha512-wSNn+TcCh+DuE2pdg058K3MhxA4g+IiZlW7yGz4cMd/t3z2rJXZcV3HDxZljbrICU2Iq0qY4UHnbolTMK/+bcA==}
dev: false
/didyoumean@1.2.2: /didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true dev: true
@ -4875,6 +4904,10 @@ packages:
has-symbols: 1.0.3 has-symbols: 1.0.3
hasown: 2.0.2 hasown: 2.0.2
/get-node-dimensions@1.2.1:
resolution: {integrity: sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ==}
dev: false
/get-package-type@0.1.0: /get-package-type@0.1.0:
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
@ -6384,6 +6417,10 @@ packages:
boolbase: 1.0.0 boolbase: 1.0.0
dev: true dev: true
/numeral@2.0.6:
resolution: {integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==}
dev: false
/nwsapi@2.2.7: /nwsapi@2.2.7:
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
dev: true dev: true
@ -7138,6 +7175,20 @@ packages:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
dev: false dev: false
/react-measure@2.5.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA==}
peerDependencies:
react: '>0.13.0'
react-dom: '>0.13.0'
dependencies:
'@babel/runtime': 7.24.4
get-node-dimensions: 1.2.1
prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
resize-observer-polyfill: 1.5.1
dev: false
/react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0): /react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==} resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==}
peerDependencies: peerDependencies:
@ -7452,6 +7503,10 @@ packages:
resolution: {integrity: sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==} resolution: {integrity: sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==}
dev: false dev: false
/resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
dev: false
/resolve-cwd@3.0.0: /resolve-cwd@3.0.0:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -7495,6 +7550,11 @@ packages:
signal-exit: 3.0.7 signal-exit: 3.0.7
dev: true dev: true
/retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
dev: false
/reusify@1.0.4: /reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'} engines: {iojs: '>=1.0.0', node: '>=0.10.0'}

View File

@ -1,4 +1,4 @@
import Y from 'yjs'; import * as Y from 'yjs';
export type BlockId = string; export type BlockId = string;
@ -8,6 +8,10 @@ export type ChildrenId = string;
export type ViewId = string; export type ViewId = string;
export type RowId = string;
export type CellId = string;
export enum BlockType { export enum BlockType {
Paragraph = 'paragraph', Paragraph = 'paragraph',
Page = 'page', Page = 'page',
@ -192,6 +196,51 @@ export enum YjsFolderKey {
type = 'ty', type = 'ty',
value = 'value', value = 'value',
layout = 'layout', layout = 'layout',
bid = 'bid',
}
export enum YjsDatabaseKey {
views = 'views',
id = 'id',
metas = 'metas',
fields = 'fields',
is_primary = 'is_primary',
last_modified = 'last_modified',
created_at = 'created_at',
name = 'name',
type = 'ty',
type_option = 'type_option',
content = 'content',
data = 'data',
iid = 'iid',
database_id = 'database_id',
field_orders = 'field_orders',
field_settings = 'field_settings',
visibility = 'visibility',
wrap = 'wrap',
width = 'width',
filters = 'filters',
groups = 'groups',
layout = 'layout',
layout_settings = 'layout_settings',
modified_at = 'modified_at',
row_orders = 'row_orders',
sorts = 'sorts',
height = 'height',
cells = 'cells',
field_type = 'field_type',
end_timestamp = 'end_timestamp',
include_time = 'include_time',
is_range = 'is_range',
reminder_id = 'reminder_id',
time_format = 'time_format',
date_format = 'date_format',
calculations = 'calculations',
field_id = 'field_id',
calculation_value = 'calculation_value',
condition = 'condition',
format = 'format',
filter_type = 'filter_type',
} }
export interface YDoc extends Y.Doc { export interface YDoc extends Y.Doc {
@ -199,11 +248,54 @@ export interface YDoc extends Y.Doc {
getMap(key: YjsEditorKey.data_section): YSharedRoot | any; getMap(key: YjsEditorKey.data_section): YSharedRoot | any;
} }
export interface YDatabaseRow extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): RowId;
get(key: YjsDatabaseKey.height): string;
get(key: YjsDatabaseKey.visibility): boolean;
get(key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.last_modified): LastModified;
get(key: YjsDatabaseKey.cells): YDatabaseCells;
}
export interface YDatabaseCells extends Y.Map<unknown> {
get(key: FieldId): YDatabaseCell;
}
export type EndTimestamp = string;
export type ReminderId = string;
export interface YDatabaseCell extends Y.Map<unknown> {
get(key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.last_modified): LastModified;
get(key: YjsDatabaseKey.field_type): string;
get(key: YjsDatabaseKey.data): object | string | boolean | number;
get(key: YjsDatabaseKey.end_timestamp): EndTimestamp;
get(key: YjsDatabaseKey.include_time): boolean;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.is_range): boolean;
get(key: YjsDatabaseKey.reminder_id): ReminderId;
}
export interface YSharedRoot extends Y.Map<unknown> { export interface YSharedRoot extends Y.Map<unknown> {
get(key: YjsEditorKey.document): YDocument; get(key: YjsEditorKey.document): YDocument;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsEditorKey.folder): YFolder; get(key: YjsEditorKey.folder): YFolder;
get(key: YjsEditorKey.database): YDatabase;
get(key: YjsEditorKey.database_row): YDatabaseRow;
} }
export interface YFolder extends Y.Map<unknown> { export interface YFolder extends Y.Map<unknown> {
@ -226,6 +318,9 @@ export interface YViews extends Y.Map<unknown> {
export interface YView extends Y.Map<unknown> { export interface YView extends Y.Map<unknown> {
get(key: YjsFolderKey.id): ViewId; get(key: YjsFolderKey.id): ViewId;
get(key: YjsFolderKey.bid): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.name): string; get(key: YjsFolderKey.name): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
@ -271,6 +366,166 @@ export interface YTextMap extends Y.Map<unknown> {
get(key: ExternalId): Y.Text; get(key: ExternalId): Y.Text;
} }
export interface YDatabase extends Y.Map<unknown> {
get(key: YjsDatabaseKey.views): YDatabaseViews;
get(key: YjsDatabaseKey.metas): YDatabaseMetas;
get(key: YjsDatabaseKey.fields): YDatabaseFields;
get(key: YjsDatabaseKey.id): string;
}
export interface YDatabaseViews extends Y.Map<unknown> {
get(key: ViewId): YDatabaseView;
}
export type DatabaseId = string;
export type CreatedAt = string;
export type LastModified = string;
export type ModifiedAt = string;
export type FieldId = string;
export enum DatabaseViewLayout {
Grid = 0,
Board = 1,
Calendar = 2,
}
export interface YDatabaseView extends Y.Map<unknown> {
get(key: YjsDatabaseKey.database_id): DatabaseId;
get(key: YjsDatabaseKey.name): string;
get(key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.modified_at): ModifiedAt;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.layout): string;
get(key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings;
get(key: YjsDatabaseKey.filters): YDatabaseFilters;
get(key: YjsDatabaseKey.groups): YDatabaseGroups;
get(key: YjsDatabaseKey.sorts): YDatabaseSorts;
get(key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings;
get(key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders;
get(key: YjsDatabaseKey.row_orders): YDatabaseRowOrders;
get(key: YjsDatabaseKey.calculations): YDatabaseCalculations;
}
export type YDatabaseFieldOrders = Y.Array<unknown>; // [ { id: FieldId } ]
export type YDatabaseRowOrders = Y.Array<YDatabaseRowOrder>; // [ { id: RowId, height: number } ]
export type YDatabaseGroups = Y.Array<unknown>;
export type YDatabaseFilters = Y.Array<YDatabaseFilter>;
export type YDatabaseSorts = Y.Array<YDatabaseSort>;
export type YDatabaseLayoutSettings = Y.Map<unknown>;
export type YDatabaseCalculations = Y.Array<YDatabaseCalculation>;
export type SortId = string;
export interface YDatabaseRowOrder extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): SortId;
get(key: YjsDatabaseKey.height): number;
}
export interface YDatabaseSort extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): SortId;
get(key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.condition): string;
}
export type FilterId = string;
export interface YDatabaseFilter extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): FilterId;
get(key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string;
}
export interface YDatabaseCalculation extends Y.Map<unknown> {
get(key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string;
}
export interface YDatabaseFieldSettings extends Y.Map<unknown> {
get(key: FieldId): YDatabaseFieldSetting;
}
export interface YDatabaseFieldSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.visibility): string;
get(key: YjsDatabaseKey.wrap): boolean;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.width): string;
}
export interface YDatabaseMetas extends Y.Map<unknown> {
get(key: YjsDatabaseKey.iid): string;
}
export interface YDatabaseFields extends Y.Map<unknown> {
get(key: FieldId): YDatabaseField;
}
export interface YDatabaseField extends Y.Map<unknown> {
get(key: YjsDatabaseKey.name): string;
get(key: YjsDatabaseKey.id): FieldId;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.type): string;
get(key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption;
get(key: YjsDatabaseKey.is_primary): boolean;
get(key: YjsDatabaseKey.last_modified): LastModified;
}
export interface YDatabaseFieldTypeOption extends Y.Map<unknown> {
// key is the field type
get(key: string): YMapFieldTypeOption;
}
export interface YMapFieldTypeOption extends Y.Map<unknown> {
get(key: YjsDatabaseKey.content): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.data): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.time_format): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.date_format): string;
get(key: YjsDatabaseKey.database_id): DatabaseId;
// eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.format): string;
}
export enum CollabType { export enum CollabType {
Document = 0, Document = 0,
Database = 1, Database = 1,
@ -282,8 +537,12 @@ export enum CollabType {
} }
export enum CollabOrigin { export enum CollabOrigin {
// from local changes and never sync to remote. used for read-only mode
Local = 'local', Local = 'local',
// from remote changes and never sync to remote.
Remote = 'remote', Remote = 'remote',
// from local changes and sync to remote. used for collaborative mode
LocalSync = 'local_sync',
} }
export const layoutMap = { export const layoutMap = {
@ -292,3 +551,9 @@ export const layoutMap = {
[ViewLayout.Board]: 'board', [ViewLayout.Board]: 'board',
[ViewLayout.Calendar]: 'calendar', [ViewLayout.Calendar]: 'calendar',
}; };
export const databaseLayoutMap = {
[DatabaseViewLayout.Grid]: 'grid',
[DatabaseViewLayout.Board]: 'board',
[DatabaseViewLayout.Calendar]: 'calendar',
};

View File

@ -0,0 +1,2 @@
export const DEFAULT_ROW_HEIGHT = 37;
export const MIN_COLUMN_WIDTH = 100;

View File

@ -0,0 +1,127 @@
import { YDatabase, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
import { filterBy } from '@/application/database-yjs/filter';
import { Row } from '@/application/database-yjs/selector';
import { sortBy } from '@/application/database-yjs/sort';
import { createContext, useContext, useEffect, useState } from 'react';
import * as Y from 'yjs';
import debounce from 'lodash-es/debounce';
export interface DatabaseContextState {
readOnly: boolean;
doc: YDoc;
viewId: string;
rowDocMap: Y.Map<YDoc>;
}
export const DatabaseContext = createContext<DatabaseContextState | null>(null);
export const useDatabase = () => {
const database = useContext(DatabaseContext)
?.doc?.getMap(YjsEditorKey.data_section)
.get(YjsEditorKey.database) as YDatabase;
return database;
};
export const useRowMeta = (rowId: string) => {
const rows = useContext(DatabaseContext)?.rowDocMap;
const rowMetaDoc = rows?.get(rowId);
const rowMeta = rowMetaDoc?.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow;
return rowMeta;
};
export const useViewId = () => {
const context = useContext(DatabaseContext);
return context?.viewId;
};
export const useReadOnly = () => {
const context = useContext(DatabaseContext);
return context?.readOnly;
};
export const useDatabaseView = () => {
const database = useDatabase();
const viewId = useViewId();
return viewId ? database.get(YjsDatabaseKey.views)?.get(viewId) : undefined;
};
export function useDatabaseFields() {
const database = useDatabase();
return database.get(YjsDatabaseKey.fields);
}
export interface GridRowsState {
rowOrders: Row[];
}
export const GridRowsContext = createContext<GridRowsState | null>(null);
export function useGridRowsContext() {
return useContext(GridRowsContext);
}
export function useGridRows() {
return useGridRowsContext()?.rowOrders;
}
export function useGridRowOrders() {
const rows = useContext(DatabaseContext)?.rowDocMap;
const [rowOrders, setRowOrders] = useState<Row[]>();
const view = useDatabaseView();
const sorts = view?.get(YjsDatabaseKey.sorts);
const fields = useDatabaseFields();
const filters = view?.get(YjsDatabaseKey.filters);
useEffect(() => {
const onConditionsChange = () => {
const originalRowOrders = view?.get(YjsDatabaseKey.row_orders).toJSON();
if (!originalRowOrders || !rows) return;
console.log('sort or filter changed');
if (sorts?.length === 0 && filters?.length === 0) {
setRowOrders(originalRowOrders);
return;
}
let rowOrders: Row[] | undefined;
if (sorts?.length) {
rowOrders = sortBy(originalRowOrders, sorts, fields, rows);
}
if (filters?.length) {
rowOrders = filterBy(rowOrders ?? originalRowOrders, filters, fields, rows);
}
if (rowOrders) {
setRowOrders(rowOrders);
} else {
setRowOrders(originalRowOrders);
}
};
const debounceConditionsChange = debounce(onConditionsChange, 200);
onConditionsChange();
sorts?.observeDeep(debounceConditionsChange);
filters?.observeDeep(debounceConditionsChange);
fields?.observeDeep(debounceConditionsChange);
rows?.observeDeep(debounceConditionsChange);
return () => {
sorts?.unobserveDeep(debounceConditionsChange);
filters?.unobserveDeep(debounceConditionsChange);
fields?.unobserveDeep(debounceConditionsChange);
rows?.observeDeep(debounceConditionsChange);
};
}, [fields, rows, sorts, filters, view]);
return rowOrders;
}

View File

@ -0,0 +1,51 @@
import { FieldId } from '@/application/collab.type';
export enum FieldVisibility {
AlwaysShown = 0,
HideWhenEmpty = 1,
AlwaysHidden = 2,
}
export enum FieldType {
RichText = 0,
Number = 1,
DateTime = 2,
SingleSelect = 3,
MultiSelect = 4,
Checkbox = 5,
URL = 6,
Checklist = 7,
LastEditedTime = 8,
CreatedTime = 9,
Relation = 10,
}
export enum CalculationType {
Average = 0,
Max = 1,
Median = 2,
Min = 3,
Sum = 4,
Count = 5,
CountEmpty = 6,
CountNonEmpty = 7,
}
export enum SortCondition {
Ascending = 0,
Descending = 1,
}
export enum FilterType {
Data = 0,
And = 1,
Or = 2,
}
export interface Filter {
fieldId: FieldId;
filterType: FilterType;
condition: number;
id: string;
content: string;
}

View File

@ -0,0 +1,10 @@
import { Filter } from '@/application/database-yjs';
export enum CheckboxFilterCondition {
IsChecked = 0,
IsUnChecked = 1,
}
export interface CheckboxFilter extends Filter {
condition: CheckboxFilterCondition;
}

View File

@ -0,0 +1 @@
export * from './checkbox.type';

View File

@ -0,0 +1,10 @@
import { Filter } from '@/application/database-yjs';
export enum ChecklistFilterCondition {
IsComplete = 0,
IsIncomplete = 1,
}
export interface ChecklistFilter extends Filter {
condition: ChecklistFilterCondition;
}

View File

@ -0,0 +1,2 @@
export * from './checklist.type';
export * from './parse';

View File

@ -0,0 +1,22 @@
import { SelectOption } from '../select-option';
export interface ChecklistCellData {
selectedOptionIds?: string[];
options?: SelectOption[];
percentage: number;
}
export function parseChecklistData(data: string): ChecklistCellData | null {
try {
const { options, selected_option_ids } = JSON.parse(data);
const percentage = (selected_option_ids.length / options.length) * 100;
return {
percentage,
options,
selectedOptionIds: selected_option_ids,
};
} catch (e) {
return null;
}
}

View File

@ -0,0 +1,32 @@
import { Filter } from '@/application/database-yjs';
export enum TimeFormat {
TwelveHour = 0,
TwentyFourHour = 1,
}
export enum DateFormat {
Local = 0,
US = 1,
ISO = 2,
Friendly = 3,
DayMonthYear = 4,
}
export enum DateFilterCondition {
DateIs = 0,
DateBefore = 1,
DateAfter = 2,
DateOnOrBefore = 3,
DateOnOrAfter = 4,
DateWithIn = 5,
DateIsEmpty = 6,
DateIsNotEmpty = 7,
}
export interface DateFilter extends Filter {
condition: DateFilterCondition;
start?: number;
end?: number;
timestamp?: number;
}

View File

@ -0,0 +1,2 @@
export * from './date.type';
export * from './utils';

View File

@ -0,0 +1,29 @@
import { TimeFormat, DateFormat } from '@/application/database-yjs';
export function getTimeFormat(timeFormat?: TimeFormat) {
switch (timeFormat) {
case TimeFormat.TwelveHour:
return 'h:mm A';
case TimeFormat.TwentyFourHour:
return 'HH:mm';
default:
return 'HH:mm';
}
}
export function getDateFormat(dateFormat?: DateFormat) {
switch (dateFormat) {
case DateFormat.Friendly:
return 'MMM DD, YYYY';
case DateFormat.ISO:
return 'YYYY-MM-DD';
case DateFormat.US:
return 'YYYY/MM/DD';
case DateFormat.Local:
return 'MM/DD/YYYY';
case DateFormat.DayMonthYear:
return 'DD/MM/YYYY';
default:
return 'YYYY-MM-DD';
}
}

View File

@ -0,0 +1,8 @@
export * from './type_option';
export * from './date';
export * from './number';
export * from './select-option';
export * from './text';
export * from './checkbox';
export * from './checklist';
export * from './relation';

View File

@ -0,0 +1,628 @@
import { currencyFormaterMap } from '../format';
import { NumberFormat } from '../number.type';
import { expect } from '@jest/globals';
const testCases = [0, 1, 0.5, 0.5666, 1000, 10000, 1000000, 10000000, 1000000.0];
describe('currencyFormaterMap', () => {
test('should return the correct formatter for Num', () => {
const formater = currencyFormaterMap[NumberFormat.Num];
const result = ['0', '1', '0.5', '0.5666', '1,000', '10,000', '1,000,000', '10,000,000', '1,000,000'];
testCases.forEach((testCase) => {
expect(formater(testCase)).toBe(result[testCases.indexOf(testCase)]);
});
});
test('should return the correct formatter for Percent', () => {
const formater = currencyFormaterMap[NumberFormat.Percent];
const result = ['0%', '1%', '0.5%', '0.57%', '1,000%', '10,000%', '1,000,000%', '10,000,000%', '1,000,000%'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for USD', () => {
const formater = currencyFormaterMap[NumberFormat.USD];
const result = ['$0', '$1', '$0.5', '$0.57', '$1,000', '$10,000', '$1,000,000', '$10,000,000', '$1,000,000'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for CanadianDollar', () => {
const formater = currencyFormaterMap[NumberFormat.CanadianDollar];
const result = [
'CA$0',
'CA$1',
'CA$0.5',
'CA$0.57',
'CA$1,000',
'CA$10,000',
'CA$1,000,000',
'CA$10,000,000',
'CA$1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for EUR', () => {
const formater = currencyFormaterMap[NumberFormat.EUR];
const result = ['€0', '€1', '€0.5', '€0.57', '€1,000', '€10,000', '€1,000,000', '€10,000,000', '€1,000,000'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Pound', () => {
const formater = currencyFormaterMap[NumberFormat.Pound];
const result = ['£0', '£1', '£0.5', '£0.57', '£1,000', '£10,000', '£1,000,000', '£10,000,000', '£1,000,000'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Yen', () => {
const formater = currencyFormaterMap[NumberFormat.Yen];
const result = [
'¥0',
'¥1',
'¥0.5',
'¥0.57',
'¥1,000',
'¥10,000',
'¥1,000,000',
'¥10,000,000',
'¥1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Ruble', () => {
const formater = currencyFormaterMap[NumberFormat.Ruble];
const result = [
'0 RUB',
'1 RUB',
'0,5 RUB',
'0,57 RUB',
'1 000 RUB',
'10 000 RUB',
'1 000 000 RUB',
'10 000 000 RUB',
'1 000 000 RUB',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Rupee', () => {
const formater = currencyFormaterMap[NumberFormat.Rupee];
const result = ['₹0', '₹1', '₹0.5', '₹0.57', '₹1,000', '₹10,000', '₹10,00,000', '₹1,00,00,000', '₹10,00,000'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Won', () => {
const formater = currencyFormaterMap[NumberFormat.Won];
const result = ['₩0', '₩1', '₩0.5', '₩0.57', '₩1,000', '₩10,000', '₩1,000,000', '₩10,000,000', '₩1,000,000'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Yuan', () => {
const formater = currencyFormaterMap[NumberFormat.Yuan];
const result = [
'CN¥0',
'CN¥1',
'CN¥0.5',
'CN¥0.57',
'CN¥1,000',
'CN¥10,000',
'CN¥1,000,000',
'CN¥10,000,000',
'CN¥1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Real', () => {
const formater = currencyFormaterMap[NumberFormat.Real];
const result = [
'R$ 0',
'R$ 1',
'R$ 0,5',
'R$ 0,57',
'R$ 1.000',
'R$ 10.000',
'R$ 1.000.000',
'R$ 10.000.000',
'R$ 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Lira', () => {
const formater = currencyFormaterMap[NumberFormat.Lira];
const result = [
'TRY 0',
'TRY 1',
'TRY 0,5',
'TRY 0,57',
'TRY 1.000',
'TRY 10.000',
'TRY 1.000.000',
'TRY 10.000.000',
'TRY 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Rupiah', () => {
const formater = currencyFormaterMap[NumberFormat.Rupiah];
const result = [
'IDR 0',
'IDR 1',
'IDR 0,5',
'IDR 0,57',
'IDR 1.000',
'IDR 10.000',
'IDR 1.000.000',
'IDR 10.000.000',
'IDR 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Franc', () => {
const formater = currencyFormaterMap[NumberFormat.Franc];
const result = [
'CHF 0',
'CHF 1',
'CHF 0.5',
'CHF 0.57',
`CHF 1000`,
`CHF 10000`,
`CHF 1000000`,
`CHF 10000000`,
`CHF 1000000`,
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for HongKongDollar', () => {
const formater = currencyFormaterMap[NumberFormat.HongKongDollar];
const result = [
'HK$0',
'HK$1',
'HK$0.5',
'HK$0.57',
'HK$1,000',
'HK$10,000',
'HK$1,000,000',
'HK$10,000,000',
'HK$1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for NewZealandDollar', () => {
const formater = currencyFormaterMap[NumberFormat.NewZealandDollar];
const result = [
'NZ$0',
'NZ$1',
'NZ$0.5',
'NZ$0.57',
'NZ$1,000',
'NZ$10,000',
'NZ$1,000,000',
'NZ$10,000,000',
'NZ$1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Krona', () => {
const formater = currencyFormaterMap[NumberFormat.Krona];
const result = [
'0 SEK',
'1 SEK',
'0,5 SEK',
'0,57 SEK',
'1 000 SEK',
'10 000 SEK',
'1 000 000 SEK',
'10 000 000 SEK',
'1 000 000 SEK',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for NorwegianKrone', () => {
const formater = currencyFormaterMap[NumberFormat.NorwegianKrone];
const result = [
'NOK 0',
'NOK 1',
'NOK 0,5',
'NOK 0,57',
'NOK 1 000',
'NOK 10 000',
'NOK 1 000 000',
'NOK 10 000 000',
'NOK 1 000 000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for MexicanPeso', () => {
const formater = currencyFormaterMap[NumberFormat.MexicanPeso];
const result = [
'MX$0',
'MX$1',
'MX$0.5',
'MX$0.57',
'MX$1,000',
'MX$10,000',
'MX$1,000,000',
'MX$10,000,000',
'MX$1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Rand', () => {
const formater = currencyFormaterMap[NumberFormat.Rand];
const result = [
'ZAR 0',
'ZAR 1',
'ZAR 0,5',
'ZAR 0,57',
'ZAR 1 000',
'ZAR 10 000',
'ZAR 1 000 000',
'ZAR 10 000 000',
'ZAR 1 000 000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for NewTaiwanDollar', () => {
const formater = currencyFormaterMap[NumberFormat.NewTaiwanDollar];
const result = [
'NT$0',
'NT$1',
'NT$0.5',
'NT$0.57',
'NT$1,000',
'NT$10,000',
'NT$1,000,000',
'NT$10,000,000',
'NT$1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for DanishKrone', () => {
const formater = currencyFormaterMap[NumberFormat.DanishKrone];
const result = [
'0 DKK',
'1 DKK',
'0,5 DKK',
'0,57 DKK',
'1.000 DKK',
'10.000 DKK',
'1.000.000 DKK',
'10.000.000 DKK',
'1.000.000 DKK',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Baht', () => {
const formater = currencyFormaterMap[NumberFormat.Baht];
const result = [
'THB 0',
'THB 1',
'THB 0.5',
'THB 0.57',
'THB 1,000',
'THB 10,000',
'THB 1,000,000',
'THB 10,000,000',
'THB 1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Forint', () => {
const formater = currencyFormaterMap[NumberFormat.Forint];
const result = [
'0 HUF',
'1 HUF',
'0,5 HUF',
'0,57 HUF',
'1 000 HUF',
'10 000 HUF',
'1 000 000 HUF',
'10 000 000 HUF',
'1 000 000 HUF',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Koruna', () => {
const formater = currencyFormaterMap[NumberFormat.Koruna];
const result = [
'0 CZK',
'1 CZK',
'0,5 CZK',
'0,57 CZK',
'1 000 CZK',
'10 000 CZK',
'1 000 000 CZK',
'10 000 000 CZK',
'1 000 000 CZK',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Shekel', () => {
const formater = currencyFormaterMap[NumberFormat.Shekel];
const result = [
'0 ₪',
'1 ₪',
'0.5 ‏₪',
'0.57 ‏₪',
'1,000 ₪',
'10,000 ₪',
'1,000,000 ₪',
'10,000,000 ₪',
'1,000,000 ₪',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for ChileanPeso', () => {
const formater = currencyFormaterMap[NumberFormat.ChileanPeso];
const result = [
'CLP 0',
'CLP 1',
'CLP 0,5',
'CLP 0,57',
'CLP 1.000',
'CLP 10.000',
'CLP 1.000.000',
'CLP 10.000.000',
'CLP 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for PhilippinePeso', () => {
const formater = currencyFormaterMap[NumberFormat.PhilippinePeso];
const result = ['₱0', '₱1', '₱0.5', '₱0.57', '₱1,000', '₱10,000', '₱1,000,000', '₱10,000,000', '₱1,000,000'];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Dirham', () => {
const formater = currencyFormaterMap[NumberFormat.Dirham];
const result = [
'0 AED',
'1 AED',
'0.5 AED',
'0.57 AED',
'1,000 AED',
'10,000 AED',
'1,000,000 AED',
'10,000,000 AED',
'1,000,000 AED',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for ColombianPeso', () => {
const formater = currencyFormaterMap[NumberFormat.ColombianPeso];
const result = [
'COP 0',
'COP 1',
'COP 0,5',
'COP 0,57',
'COP 1.000',
'COP 10.000',
'COP 1.000.000',
'COP 10.000.000',
'COP 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Riyal', () => {
const formater = currencyFormaterMap[NumberFormat.Riyal];
const result = [
'SAR 0',
'SAR 1',
'SAR 0.5',
'SAR 0.57',
'SAR 1,000',
'SAR 10,000',
'SAR 1,000,000',
'SAR 10,000,000',
'SAR 1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Ringgit', () => {
const formater = currencyFormaterMap[NumberFormat.Ringgit];
const result = [
'RM 0',
'RM 1',
'RM 0.5',
'RM 0.57',
'RM 1,000',
'RM 10,000',
'RM 1,000,000',
'RM 10,000,000',
'RM 1,000,000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for Leu', () => {
const formater = currencyFormaterMap[NumberFormat.Leu];
const result = [
'0 RON',
'1 RON',
'0,5 RON',
'0,57 RON',
'1.000 RON',
'10.000 RON',
'1.000.000 RON',
'10.000.000 RON',
'1.000.000 RON',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for ArgentinePeso', () => {
const formater = currencyFormaterMap[NumberFormat.ArgentinePeso];
const result = [
'ARS 0',
'ARS 1',
'ARS 0,5',
'ARS 0,57',
'ARS 1.000',
'ARS 10.000',
'ARS 1.000.000',
'ARS 10.000.000',
'ARS 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
test('should return the correct formatter for UruguayanPeso', () => {
const formater = currencyFormaterMap[NumberFormat.UruguayanPeso];
const result = [
'UYU 0',
'UYU 1',
'UYU 0,5',
'UYU 0,57',
'UYU 1.000',
'UYU 10.000',
'UYU 1.000.000',
'UYU 10.000.000',
'UYU 1.000.000',
];
testCases.forEach((testCase, index) => {
expect(formater(testCase)).toBe(result[index]);
});
});
});

View File

@ -0,0 +1,229 @@
import { NumberFormat } from './number.type';
const commonProps = {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
style: 'currency',
currencyDisplay: 'symbol',
useGrouping: true,
};
export const currencyFormaterMap: Record<NumberFormat, (n: number) => string> = {
[NumberFormat.Num]: (n: number) =>
new Intl.NumberFormat('en-US', {
style: 'decimal',
minimumFractionDigits: 0,
maximumFractionDigits: 20,
}).format(n),
[NumberFormat.Percent]: (n: number) =>
new Intl.NumberFormat('en-US', {
...commonProps,
style: 'decimal',
}).format(n) + '%',
[NumberFormat.USD]: (n: number) =>
new Intl.NumberFormat('en-US', {
...commonProps,
currency: 'USD',
}).format(n),
[NumberFormat.CanadianDollar]: (n: number) =>
new Intl.NumberFormat('en-CA', {
...commonProps,
currency: 'CAD',
})
.format(n)
.replace('$', 'CA$'),
[NumberFormat.EUR]: (n: number) =>
new Intl.NumberFormat('en-IE', {
...commonProps,
currency: 'EUR',
}).format(n),
[NumberFormat.Pound]: (n: number) =>
new Intl.NumberFormat('en-GB', {
...commonProps,
currency: 'GBP',
}).format(n),
[NumberFormat.Yen]: (n: number) =>
new Intl.NumberFormat('ja-JP', {
...commonProps,
currency: 'JPY',
}).format(n),
[NumberFormat.Ruble]: (n: number) =>
new Intl.NumberFormat('ru-RU', {
...commonProps,
currency: 'RUB',
currencyDisplay: 'code',
})
.format(n)
.replaceAll(' ', ' '),
[NumberFormat.Rupee]: (n: number) =>
new Intl.NumberFormat('hi-IN', {
...commonProps,
currency: 'INR',
}).format(n),
[NumberFormat.Won]: (n: number) =>
new Intl.NumberFormat('ko-KR', {
...commonProps,
currency: 'KRW',
}).format(n),
[NumberFormat.Yuan]: (n: number) =>
new Intl.NumberFormat('zh-CN', {
...commonProps,
currency: 'CNY',
})
.format(n)
.replace('¥', 'CN¥'),
[NumberFormat.Real]: (n: number) =>
new Intl.NumberFormat('pt-BR', {
...commonProps,
currency: 'BRL',
})
.format(n)
.replaceAll(' ', ' '),
[NumberFormat.Lira]: (n: number) =>
new Intl.NumberFormat('tr-TR', {
...commonProps,
currency: 'TRY',
currencyDisplay: 'code',
})
.format(n)
.replaceAll(' ', ' '),
[NumberFormat.Rupiah]: (n: number) =>
new Intl.NumberFormat('id-ID', {
...commonProps,
currency: 'IDR',
currencyDisplay: 'code',
})
.format(n)
.replaceAll(' ', ' '),
[NumberFormat.Franc]: (n: number) =>
new Intl.NumberFormat('de-CH', {
...commonProps,
currency: 'CHF',
})
.format(n)
.replaceAll(' ', ' '),
[NumberFormat.HongKongDollar]: (n: number) =>
new Intl.NumberFormat('zh-HK', {
...commonProps,
currency: 'HKD',
}).format(n),
[NumberFormat.NewZealandDollar]: (n: number) =>
new Intl.NumberFormat('en-NZ', {
...commonProps,
currency: 'NZD',
})
.format(n)
.replace('$', 'NZ$'),
[NumberFormat.Krona]: (n: number) =>
new Intl.NumberFormat('sv-SE', {
...commonProps,
currency: 'SEK',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.NorwegianKrone]: (n: number) =>
new Intl.NumberFormat('nb-NO', {
...commonProps,
currency: 'NOK',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.MexicanPeso]: (n: number) =>
new Intl.NumberFormat('es-MX', {
...commonProps,
currency: 'MXN',
})
.format(n)
.replace('$', 'MX$'),
[NumberFormat.Rand]: (n: number) =>
new Intl.NumberFormat('en-ZA', {
...commonProps,
currency: 'ZAR',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.NewTaiwanDollar]: (n: number) =>
new Intl.NumberFormat('zh-TW', {
...commonProps,
currency: 'TWD',
})
.format(n)
.replace('$', 'NT$'),
[NumberFormat.DanishKrone]: (n: number) =>
new Intl.NumberFormat('da-DK', {
...commonProps,
currency: 'DKK',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.Baht]: (n: number) =>
new Intl.NumberFormat('th-TH', {
...commonProps,
currency: 'THB',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.Forint]: (n: number) =>
new Intl.NumberFormat('hu-HU', {
...commonProps,
currency: 'HUF',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.Koruna]: (n: number) =>
new Intl.NumberFormat('cs-CZ', {
...commonProps,
currency: 'CZK',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.Shekel]: (n: number) =>
new Intl.NumberFormat('he-IL', {
...commonProps,
currency: 'ILS',
}).format(n),
[NumberFormat.ChileanPeso]: (n: number) =>
new Intl.NumberFormat('es-CL', {
...commonProps,
currency: 'CLP',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.PhilippinePeso]: (n: number) =>
new Intl.NumberFormat('fil-PH', {
...commonProps,
currency: 'PHP',
}).format(n),
[NumberFormat.Dirham]: (n: number) =>
new Intl.NumberFormat('ar-AE', {
...commonProps,
currency: 'AED',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.ColombianPeso]: (n: number) =>
new Intl.NumberFormat('es-CO', {
...commonProps,
currency: 'COP',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.Riyal]: (n: number) =>
new Intl.NumberFormat('en-US', {
...commonProps,
currency: 'SAR',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.Ringgit]: (n: number) =>
new Intl.NumberFormat('ms-MY', {
...commonProps,
currency: 'MYR',
}).format(n),
[NumberFormat.Leu]: (n: number) =>
new Intl.NumberFormat('ro-RO', {
...commonProps,
currency: 'RON',
}).format(n),
[NumberFormat.ArgentinePeso]: (n: number) =>
new Intl.NumberFormat('es-AR', {
...commonProps,
currency: 'ARS',
currencyDisplay: 'code',
}).format(n),
[NumberFormat.UruguayanPeso]: (n: number) =>
new Intl.NumberFormat('es-UY', {
...commonProps,
currency: 'UYU',
currencyDisplay: 'code',
}).format(n),
};

View File

@ -0,0 +1,3 @@
export * from './format';
export * from './number.type';
export * from './parse';

View File

@ -0,0 +1,56 @@
import { Filter } from '@/application/database-yjs';
export enum NumberFormat {
Num = 0,
USD = 1,
CanadianDollar = 2,
EUR = 4,
Pound = 5,
Yen = 6,
Ruble = 7,
Rupee = 8,
Won = 9,
Yuan = 10,
Real = 11,
Lira = 12,
Rupiah = 13,
Franc = 14,
HongKongDollar = 15,
NewZealandDollar = 16,
Krona = 17,
NorwegianKrone = 18,
MexicanPeso = 19,
Rand = 20,
NewTaiwanDollar = 21,
DanishKrone = 22,
Baht = 23,
Forint = 24,
Koruna = 25,
Shekel = 26,
ChileanPeso = 27,
PhilippinePeso = 28,
Dirham = 29,
ColombianPeso = 30,
Riyal = 31,
Ringgit = 32,
Leu = 33,
ArgentinePeso = 34,
UruguayanPeso = 35,
Percent = 36,
}
export enum NumberFilterCondition {
Equal = 0,
NotEqual = 1,
GreaterThan = 2,
LessThan = 3,
GreaterThanOrEqualTo = 4,
LessThanOrEqualTo = 5,
NumberIsEmpty = 6,
NumberIsNotEmpty = 7,
}
export interface NumberFilter extends Filter {
condition: NumberFilterCondition;
content: string;
}

View File

@ -0,0 +1,11 @@
import { YDatabaseField } from '@/application/collab.type';
import { getTypeOptions } from '../type_option';
import { NumberFormat } from './number.type';
export function parseNumberTypeOptions(field: YDatabaseField) {
const numberTypeOption = getTypeOptions(field)?.toJSON();
return {
format: parseInt(numberTypeOption.format) as NumberFormat,
};
}

View File

@ -0,0 +1,2 @@
export * from './parse';
export * from './relation.type';

View File

@ -0,0 +1,9 @@
import { YDatabaseField } from '@/application/collab.type';
import { RelationTypeOption } from './relation.type';
import { getTypeOptions } from '../type_option';
export function parseRelationTypeOption(field: YDatabaseField) {
const relationTypeOption = getTypeOptions(field)?.toJSON();
return relationTypeOption as RelationTypeOption;
}

View File

@ -0,0 +1,9 @@
import { Filter } from '@/application/database-yjs';
export interface RelationTypeOption {
database_id: string;
}
export interface RelationFilter extends Filter {
condition: number;
}

View File

@ -0,0 +1,2 @@
export * from './select_option.type';
export * from './parse';

View File

@ -0,0 +1,28 @@
import { YDatabaseField, YjsDatabaseKey } from '@/application/collab.type';
import { getTypeOptions } from '../type_option';
import { SelectTypeOption } from './select_option.type';
export function parseSelectOptionTypeOptions(field: YDatabaseField) {
const content = getTypeOptions(field)?.get(YjsDatabaseKey.content);
if (!content) return null;
try {
return JSON.parse(content) as SelectTypeOption;
} catch (e) {
return null;
}
}
export function parseSelectOptionCellData(field: YDatabaseField, data: string) {
const typeOption = parseSelectOptionTypeOptions(field);
const selectedIds = typeof data === 'string' ? data.split(',') : [];
return selectedIds
.map((id) => {
const option = typeOption?.options?.find((option) => option.id === id);
return option?.name ?? '';
})
.join(', ');
}

View File

@ -0,0 +1,38 @@
import { Filter } from '@/application/database-yjs';
export enum SelectOptionColor {
Purple = 'Purple',
Pink = 'Pink',
LightPink = 'LightPink',
Orange = 'Orange',
Yellow = 'Yellow',
Lime = 'Lime',
Green = 'Green',
Aqua = 'Aqua',
Blue = 'Blue',
}
export enum SelectOptionFilterCondition {
OptionIs = 0,
OptionIsNot = 1,
OptionContains = 2,
OptionDoesNotContain = 3,
OptionIsEmpty = 4,
OptionIsNotEmpty = 5,
}
export interface SelectOptionFilter extends Filter {
condition: SelectOptionFilterCondition;
optionIds: string[];
}
export interface SelectOption {
id: string;
name: string;
color: SelectOptionColor;
}
export interface SelectTypeOption {
disable_color: boolean;
options: SelectOption[];
}

View File

@ -0,0 +1 @@
export * from './text.type';

View File

@ -0,0 +1,17 @@
import { Filter } from '@/application/database-yjs';
export enum TextFilterCondition {
TextIs = 0,
TextIsNot = 1,
TextContains = 2,
TextDoesNotContain = 3,
TextStartsWith = 4,
TextEndsWith = 5,
TextIsEmpty = 6,
TextIsNotEmpty = 7,
}
export interface TextFilter extends Filter {
condition: TextFilterCondition;
content: string;
}

View File

@ -0,0 +1,8 @@
import { YDatabaseField, YjsDatabaseKey } from '@/application/collab.type';
import { FieldType } from '@/application/database-yjs';
export function getTypeOptions(field: YDatabaseField) {
const fieldType = Number(field?.get(YjsDatabaseKey.type)) as FieldType;
return field?.get(YjsDatabaseKey.type_option)?.get(String(fieldType));
}

View File

@ -0,0 +1,223 @@
import {
YDatabaseFields,
YDatabaseFilter,
YDatabaseFilters,
YDatabaseRow,
YDoc,
YjsDatabaseKey,
YjsEditorKey,
} from '@/application/collab.type';
import { FieldType } from '@/application/database-yjs/database.type';
import {
CheckboxFilter,
CheckboxFilterCondition,
ChecklistFilter,
ChecklistFilterCondition,
DateFilter,
NumberFilter,
NumberFilterCondition,
parseChecklistData,
SelectOptionFilter,
SelectOptionFilterCondition,
TextFilter,
TextFilterCondition,
} from '@/application/database-yjs/fields';
import { Row } from '@/application/database-yjs/selector';
import Decimal from 'decimal.js';
import * as Y from 'yjs';
import { every, filter, some } from 'lodash-es';
export function parseFilter(fieldType: FieldType, filter: YDatabaseFilter) {
const fieldId = filter.get(YjsDatabaseKey.field_id);
const filterType = Number(filter.get(YjsDatabaseKey.filter_type));
const id = filter.get(YjsDatabaseKey.id);
const content = filter.get(YjsDatabaseKey.content);
const condition = Number(filter.get(YjsDatabaseKey.condition));
const value = {
fieldId,
filterType,
condition,
id,
content,
};
switch (fieldType) {
case FieldType.URL:
case FieldType.RichText:
return value as TextFilter;
case FieldType.Number:
return value as NumberFilter;
case FieldType.Checklist:
return value as ChecklistFilter;
case FieldType.Checkbox:
return value as CheckboxFilter;
case FieldType.SingleSelect:
case FieldType.MultiSelect:
// eslint-disable-next-line no-case-declarations
const options = content.split(',');
return {
...value,
optionIds: options,
} as SelectOptionFilter;
case FieldType.DateTime:
case FieldType.CreatedTime:
case FieldType.LastEditedTime:
return value as DateFilter;
}
return value;
}
function createPredicate(conditions: ((row: Row) => boolean)[]) {
return function (item: Row) {
return every(conditions, (condition) => condition(item));
};
}
export function filterBy(rows: Row[], filters: YDatabaseFilters, fields: YDatabaseFields, rowMetas: Y.Map<YDoc>) {
const filterArray = filters.toArray();
const conditions = filterArray.map((filter) => {
return (row: { id: string }) => {
const fieldId = filter.get(YjsDatabaseKey.field_id);
const field = fields.get(fieldId);
const fieldType = Number(field.get(YjsDatabaseKey.type));
const rowId = row.id;
const rowMeta = rowMetas.get(rowId);
if (!rowMeta) return false;
const filterValue = parseFilter(fieldType, filter);
const meta = rowMeta.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow;
if (!meta) return false;
const cells = meta.get(YjsDatabaseKey.cells);
const cell = cells.get(fieldId);
if (!cell) return false;
const { condition, content } = filterValue;
switch (fieldType) {
case FieldType.URL:
case FieldType.RichText:
return textFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition);
case FieldType.Number:
return numberFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition);
case FieldType.Checkbox:
return checkboxFilterCheck(cell.get(YjsDatabaseKey.data) as string, condition);
case FieldType.SingleSelect:
case FieldType.MultiSelect:
return selectOptionFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition);
case FieldType.Checklist:
return checklistFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition);
default:
return true;
}
};
});
const predicate = createPredicate(conditions);
return filter(rows, predicate);
}
export function textFilterCheck(data: string, content: string, condition: TextFilterCondition) {
switch (condition) {
case TextFilterCondition.TextContains:
return data.includes(content);
case TextFilterCondition.TextDoesNotContain:
return !data.includes(content);
case TextFilterCondition.TextIs:
return data === content;
case TextFilterCondition.TextIsNot:
return data !== content;
case TextFilterCondition.TextIsEmpty:
return data === '';
case TextFilterCondition.TextIsNotEmpty:
return data !== '';
default:
return false;
}
}
export function numberFilterCheck(data: string, content: string, condition: number) {
const decimal = new Decimal(data).toNumber();
const filterDecimal = new Decimal(content).toNumber();
switch (condition) {
case NumberFilterCondition.Equal:
return decimal === filterDecimal;
case NumberFilterCondition.NotEqual:
return decimal !== filterDecimal;
case NumberFilterCondition.GreaterThan:
return decimal > filterDecimal;
case NumberFilterCondition.GreaterThanOrEqualTo:
return decimal >= filterDecimal;
case NumberFilterCondition.LessThan:
return decimal < filterDecimal;
case NumberFilterCondition.LessThanOrEqualTo:
return decimal <= filterDecimal;
case NumberFilterCondition.NumberIsEmpty:
return data === '';
case NumberFilterCondition.NumberIsNotEmpty:
return data !== '';
default:
return false;
}
}
export function checkboxFilterCheck(data: string, condition: number) {
switch (condition) {
case CheckboxFilterCondition.IsChecked:
return data === 'Yes';
case CheckboxFilterCondition.IsUnChecked:
return data !== 'Yes';
default:
return false;
}
}
export function checklistFilterCheck(data: string, content: string, condition: number) {
const percentage = parseChecklistData(data)?.percentage ?? 0;
if (condition === ChecklistFilterCondition.IsComplete) {
return percentage === 100;
}
return percentage !== 100;
}
export function selectOptionFilterCheck(data: string, content: string, condition: number) {
const selectedOptionIds = data.split(',');
const filterOptionIds = content.split(',');
switch (condition) {
// Ensure all filterOptionIds are included in selectedOptionIds
case SelectOptionFilterCondition.OptionIs:
return every(filterOptionIds, (option) => selectedOptionIds.includes(option));
// Ensure none of the filterOptionIds are included in selectedOptionIds
case SelectOptionFilterCondition.OptionIsNot:
return every(filterOptionIds, (option) => !selectedOptionIds.includes(option));
// Ensure at least one of the filterOptionIds is included in selectedOptionIds
case SelectOptionFilterCondition.OptionContains:
return some(filterOptionIds, (option) => selectedOptionIds.includes(option));
// Ensure at least one of the filterOptionIds is not included in selectedOptionIds
case SelectOptionFilterCondition.OptionDoesNotContain:
return some(filterOptionIds, (option) => !selectedOptionIds.includes(option));
// Ensure selectedOptionIds is empty
case SelectOptionFilterCondition.OptionIsEmpty:
return selectedOptionIds.length === 0;
// Ensure selectedOptionIds is not empty
case SelectOptionFilterCondition.OptionIsNotEmpty:
return selectedOptionIds.length !== 0;
// Default case, if no conditions match
default:
return false;
}
}

View File

@ -0,0 +1,8 @@
export * from './context';
export * from './fields';
export * from './context';
export * from './selector';
export * from './database.type';
export * from './const';
export * from './filter';
export * from './sort';

View File

@ -0,0 +1,227 @@
import { FieldId, SortId, YDatabaseField, YjsDatabaseKey } from '@/application/collab.type';
import { MIN_COLUMN_WIDTH } from '@/application/database-yjs/const';
import { useDatabase, useGridRows, useViewId } from '@/application/database-yjs/context';
import { parseFilter } from '@/application/database-yjs/filter';
import { FieldType, FieldVisibility, Filter, SortCondition } from './database.type';
import { useEffect, useMemo, useState } from 'react';
export interface Column {
fieldId: string;
width: number;
visibility: FieldVisibility;
wrap?: boolean;
}
export interface Row {
id: string;
height: number;
}
const defaultVisible = [FieldVisibility.AlwaysShown, FieldVisibility.HideWhenEmpty];
export function useGridColumnsSelector(viewId: string, visibilitys: FieldVisibility[] = defaultVisible) {
const database = useDatabase();
const [columns, setColumns] = useState<Column[]>([]);
useEffect(() => {
const view = database?.get(YjsDatabaseKey.views)?.get(viewId);
const fields = database?.get(YjsDatabaseKey.fields);
const fieldsOrder = view?.get(YjsDatabaseKey.field_orders);
const fieldSettings = view?.get(YjsDatabaseKey.field_settings);
const getColumns = () => {
if (!fields || !fieldsOrder || !fieldSettings) return [];
const fieldIds = fieldsOrder.toJSON().map((item) => item.id) as string[];
return fieldIds
.map((fieldId) => {
const setting = fieldSettings.get(fieldId);
return {
fieldId,
width: parseInt(setting?.get(YjsDatabaseKey.width)) || MIN_COLUMN_WIDTH,
visibility: parseInt(setting?.get(YjsDatabaseKey.visibility)) as FieldVisibility,
wrap: setting?.get(YjsDatabaseKey.wrap),
};
})
.filter((column) => visibilitys.includes(column.visibility));
};
const observerEvent = () => setColumns(getColumns());
setColumns(getColumns());
fieldsOrder?.observe(observerEvent);
fieldSettings?.observe(observerEvent);
return () => {
fieldsOrder?.unobserve(observerEvent);
fieldSettings?.unobserve(observerEvent);
};
}, [database, viewId, visibilitys]);
return columns;
}
export function useGridRowsSelector() {
const rowOrders = useGridRows();
return useMemo(() => rowOrders ?? [], [rowOrders]);
}
export function useFieldSelector(fieldId: string) {
const database = useDatabase();
const [field, setField] = useState<YDatabaseField | null>(null);
const [clock, setClock] = useState<number>(0);
useEffect(() => {
if (!database) return;
const field = database.get(YjsDatabaseKey.fields)?.get(fieldId);
setField(field || null);
const observerEvent = () => setClock((prev) => prev + 1);
field.observe(observerEvent);
return () => {
field.unobserve(observerEvent);
};
}, [database, fieldId]);
return {
field,
clock,
};
}
export function useFiltersSelector() {
const database = useDatabase();
const viewId = useViewId();
const [filters, setFilters] = useState<string[]>([]);
useEffect(() => {
if (!viewId) return;
const view = database?.get(YjsDatabaseKey.views)?.get(viewId);
const filterOrders = view?.get(YjsDatabaseKey.filters);
if (!filterOrders) return;
const getFilters = () => {
return filterOrders.toJSON().map((item) => item.id);
};
const observerEvent = () => setFilters(getFilters());
setFilters(getFilters());
filterOrders.observe(observerEvent);
return () => {
filterOrders.unobserve(observerEvent);
};
}, [database, viewId]);
return filters;
}
export function useFilterSelector(filterId: string) {
const database = useDatabase();
const viewId = useViewId();
const fields = database?.get(YjsDatabaseKey.fields);
const [filterValue, setFilterValue] = useState<Filter | null>(null);
useEffect(() => {
if (!viewId) return;
const view = database?.get(YjsDatabaseKey.views)?.get(viewId);
const filter = view
?.get(YjsDatabaseKey.filters)
.toArray()
.find((filter) => filter.get(YjsDatabaseKey.id) === filterId);
const field = fields?.get(filter?.get(YjsDatabaseKey.field_id) as FieldId);
const observerEvent = () => {
if (!filter || !field) return;
const fieldType = Number(field.get(YjsDatabaseKey.type)) as FieldType;
setFilterValue(parseFilter(fieldType, filter));
};
observerEvent();
field?.observe(observerEvent);
filter?.observe(observerEvent);
return () => {
field?.unobserve(observerEvent);
filter?.unobserve(observerEvent);
};
}, [fields, viewId, filterId, database]);
return filterValue;
}
export function useSortsSelector() {
const database = useDatabase();
const viewId = useViewId();
const [sorts, setSorts] = useState<string[]>([]);
useEffect(() => {
if (!viewId) return;
const view = database?.get(YjsDatabaseKey.views)?.get(viewId);
const sortOrders = view?.get(YjsDatabaseKey.sorts);
if (!sortOrders) return;
const getSorts = () => {
return sortOrders.toJSON().map((item) => item.id);
};
const observerEvent = () => setSorts(getSorts());
setSorts(getSorts());
sortOrders.observe(observerEvent);
return () => {
sortOrders.unobserve(observerEvent);
};
}, [database, viewId]);
return sorts;
}
export interface Sort {
fieldId: FieldId;
condition: SortCondition;
id: SortId;
}
export function useSortSelector(sortId: SortId) {
const database = useDatabase();
const viewId = useViewId();
const [sortValue, setSortValue] = useState<Sort | null>(null);
const views = database?.get(YjsDatabaseKey.views);
useEffect(() => {
if (!viewId) return;
const view = views?.get(viewId);
const sort = view
?.get(YjsDatabaseKey.sorts)
.toArray()
.find((sort) => sort.get(YjsDatabaseKey.id) === sortId);
const observerEvent = () => {
setSortValue({
fieldId: sort?.get(YjsDatabaseKey.field_id) as FieldId,
condition: Number(sort?.get(YjsDatabaseKey.condition)),
id: sort?.get(YjsDatabaseKey.id) as SortId,
});
};
observerEvent();
sort?.observe(observerEvent);
return () => {
sort?.unobserve(observerEvent);
};
}, [viewId, sortId, views]);
return sortValue;
}

View File

@ -0,0 +1,79 @@
import {
YDatabaseField,
YDatabaseFields,
YDatabaseRow,
YDatabaseSorts,
YDoc,
YjsDatabaseKey,
YjsEditorKey,
} from '@/application/collab.type';
import { FieldType, SortCondition } from '@/application/database-yjs/database.type';
import { parseChecklistData, parseSelectOptionCellData } from '@/application/database-yjs/fields';
import { Row } from '@/application/database-yjs/selector';
import orderBy from 'lodash-es/orderBy';
import * as Y from 'yjs';
export function sortBy(rows: Row[], sorts: YDatabaseSorts, fields: YDatabaseFields, rowMetas: Y.Map<YDoc>) {
const sortArray = sorts.toArray();
const iteratees = sortArray.map((sort) => {
return (row: { id: string }) => {
const fieldId = sort.get(YjsDatabaseKey.field_id);
const field = fields.get(fieldId);
const fieldType = Number(field.get(YjsDatabaseKey.type));
const rowId = row.id;
const rowMeta = rowMetas.get(rowId);
const defaultData = parseCellDataForSort(field, '');
if (!rowMeta) return defaultData;
const meta = rowMeta.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow;
if (!meta) return defaultData;
if (fieldType === FieldType.LastEditedTime) {
return meta.get(YjsDatabaseKey.last_modified);
}
if (fieldType === FieldType.CreatedTime) {
return meta.get(YjsDatabaseKey.created_at);
}
const cells = meta.get(YjsDatabaseKey.cells);
const cell = cells.get(fieldId);
if (!cell) return defaultData;
return parseCellDataForSort(field, cell.get(YjsDatabaseKey.data) ?? '');
};
});
const orders = sortArray.map((sort) => {
const condition = Number(sort.get(YjsDatabaseKey.condition));
if (condition === SortCondition.Descending) return 'desc';
return 'asc';
});
return orderBy(rows, iteratees, orders);
}
export function parseCellDataForSort(field: YDatabaseField, data: string | boolean | number | object) {
const fieldType = Number(field.get(YjsDatabaseKey.type));
switch (fieldType) {
case FieldType.RichText:
case FieldType.URL:
case FieldType.Number:
return data;
case FieldType.Checkbox:
return data === 'Yes';
case FieldType.SingleSelect:
case FieldType.MultiSelect:
return parseSelectOptionCellData(field, typeof data === 'string' ? data : '');
case FieldType.Checklist:
return parseChecklistData(typeof data === 'string' ? data : '')?.percentage ?? 0;
case FieldType.DateTime:
return Number(data);
case FieldType.Relation:
return '';
}
}

View File

@ -1,176 +0,0 @@
import Y from 'yjs';
export type BlockId = string;
export type ExternalId = string;
export type ChildrenId = string;
export enum BlockType {
Paragraph = 'paragraph',
Page = 'page',
HeadingBlock = 'heading',
TodoListBlock = 'todo_list',
BulletedListBlock = 'bulleted_list',
NumberedListBlock = 'numbered_list',
ToggleListBlock = 'toggle_list',
CodeBlock = 'code',
EquationBlock = 'math_equation',
QuoteBlock = 'quote',
CalloutBlock = 'callout',
DividerBlock = 'divider',
ImageBlock = 'image',
GridBlock = 'grid',
OutlineBlock = 'outline',
TableBlock = 'table',
TableCell = 'table/cell',
}
export enum InlineBlockType {
Formula = 'formula',
Mention = 'mention',
}
export enum AlignType {
Left = 'left',
Center = 'center',
Right = 'right',
}
export interface BlockData {
bg_color?: string;
font_color?: string;
align?: AlignType;
}
export interface HeadingBlockData extends BlockData {
level: number;
}
export interface NumberedListBlockData extends BlockData {
number: number;
}
export interface TodoListBlockData extends BlockData {
checked: boolean;
}
export interface ToggleListBlockData extends BlockData {
collapsed: boolean;
}
export interface CodeBlockData extends BlockData {
language: string;
}
export interface CalloutBlockData extends BlockData {
icon: string;
}
export interface MathEquationBlockData extends BlockData {
formula?: string;
}
export enum ImageType {
Local = 0,
Internal = 1,
External = 2,
}
export interface ImageBlockData extends BlockData {
url?: string;
width?: number;
align?: AlignType;
image_type?: ImageType;
height?: number;
}
export interface OutlineBlockData extends BlockData {
depth?: number;
}
export interface TableBlockData extends BlockData {
colDefaultWidth: number;
colMinimumWidth: number;
colsHeight: number;
colsLen: number;
rowDefaultHeight: number;
rowsLen: number;
}
export interface TableCellBlockData extends BlockData {
colPosition: number;
height: number;
rowPosition: number;
width: number;
}
export enum MentionType {
PageRef = 'page',
Date = 'date',
}
export interface Mention {
// inline page ref id
page_id?: string;
// reminder date ref id
date?: string;
type: MentionType;
}
export enum YjsEditorKey {
data_section = 'data',
document = 'document',
database = 'database',
workspace_database = 'databases',
folder = 'folder',
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
database_row = 'data',
user_awareness = 'user_awareness',
blocks = 'blocks',
page_id = 'page_id',
meta = 'meta',
children_map = 'children_map',
text_map = 'text_map',
text = 'text',
delta = 'delta',
block_id = 'id',
block_type = 'ty',
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
block_data = 'data',
block_parent = 'parent',
block_children = 'children',
block_external_id = 'external_id',
block_external_type = 'external_type',
}
export interface YDoc extends Y.Doc {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get(key: YjsEditorKey.data_section | string): YSharedRoot | any;
}
export interface YSharedRoot extends Y.Map<unknown> {
get(key: YjsEditorKey.document): YDocument;
}
export interface YDocument extends Y.Map<unknown> {
get(key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string;
}
export interface YBlocks extends Y.Map<unknown> {
get(key: BlockId): Y.Map<unknown>;
}
export interface YMeta extends Y.Map<unknown> {
get(key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap;
}
export interface YChildrenMap extends Y.Map<unknown> {
get(key: ChildrenId): Y.Array<BlockId>;
}
export interface YTextMap extends Y.Map<unknown> {
get(key: ExternalId): Y.Text;
}

View File

@ -0,0 +1,170 @@
import { CollabOrigin, CollabType, YDatabase, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/collab.type';
import {
batchCollabs,
getCollabStorage,
getCollabStorageWithAPICall,
getUserWorkspace,
} from '@/application/services/js-services/storage';
import { DatabaseService } from '@/application/services/services.type';
import * as Y from 'yjs';
export class JSDatabaseService implements DatabaseService {
private loadedDatabaseId: Set<string> = new Set();
constructor() {
//
}
async getDatabase(
workspaceId: string,
databaseId: string
): Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}> {
const rootRowsDoc = new Y.Doc();
const rowsFolder = rootRowsDoc.getMap();
const isLoaded = this.loadedDatabaseId.has(databaseId);
let databaseDoc: YDoc | undefined = undefined;
if (isLoaded) {
databaseDoc = (await getCollabStorage(databaseId, CollabType.Database)).doc;
} else {
databaseDoc = await getCollabStorageWithAPICall(workspaceId, databaseId, CollabType.Database);
}
const database = databaseDoc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
const viewId = database.get(YjsDatabaseKey.metas)?.get(YjsDatabaseKey.iid)?.toString();
const rowOrders = database.get(YjsDatabaseKey.views)?.get(viewId)?.get(YjsDatabaseKey.row_orders);
const rowIds = rowOrders.toJSON() as {
id: string;
}[];
if (!rowIds) {
throw new Error('Database rows not found');
}
if (isLoaded) {
for (const row of rowIds) {
const { doc } = await getCollabStorage(row.id, CollabType.DatabaseRow);
rowsFolder.set(row.id, doc);
}
} else {
const rows = await this.loadDatabaseRows(
workspaceId,
rowIds.map((item) => item.id)
);
rows.forEach((row, id) => {
rowsFolder.set(id, row);
});
}
this.loadedDatabaseId.add(databaseId);
return {
databaseDoc,
rows: rowsFolder as Y.Map<YDoc>,
};
}
async openDatabase(
workspaceId: string,
viewId: string
): Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}> {
const userWorkspace = await getUserWorkspace();
if (!userWorkspace) {
throw new Error('User workspace not found');
}
const workspaceDatabaseId = userWorkspace.workspaces.find(
(workspace) => workspace.id === workspaceId
)?.workspaceDatabaseId;
if (!workspaceDatabaseId) {
throw new Error('Workspace database not found');
}
const workspaceDatabase = await getCollabStorageWithAPICall(
workspaceId,
workspaceDatabaseId,
CollabType.WorkspaceDatabase
);
const databases = workspaceDatabase
.getMap(YjsEditorKey.data_section)
.get(YjsEditorKey.workspace_database)
.toJSON() as {
views: string[];
database_id: string;
}[];
const databaseMeta = databases.find((item) => {
return item.views.some((databaseViewId: string) => databaseViewId === viewId);
});
if (!databaseMeta) {
throw new Error('Database not found');
}
const { databaseDoc, rows } = await this.getDatabase(workspaceId, databaseMeta.database_id);
const database = databaseDoc.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
const rowOrders = database.get(YjsDatabaseKey.views)?.get(viewId)?.get(YjsDatabaseKey.row_orders);
// Update rows if new rows are added
rowOrders?.observe((event) => {
if (event.changes.added.size > 0) {
const rowIds = rowOrders.toJSON() as {
id: string;
}[];
console.log('Update rows', rowIds);
void this.loadDatabaseRows(
workspaceId,
rowIds.map((item) => item.id)
).then((newRows) => {
newRows.forEach((row, id) => {
rows.set(id, row);
});
});
}
});
const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => {
if (origin === CollabOrigin.LocalSync) {
// Send the update to the server
console.log('update', update);
}
};
databaseDoc.on('update', handleUpdate);
return {
databaseDoc,
rows,
};
}
async loadDatabaseRows(workspaceId: string, rowIds: string[]) {
const rows = new Map<string, YDoc>();
try {
await batchCollabs(
workspaceId,
rowIds.map((id) => ({
object_id: id,
collab_type: CollabType.DatabaseRow,
})),
(id, rowDoc) => rows.set(id, rowDoc)
);
} catch (e) {
console.error(e);
}
return rows;
}
}

View File

@ -1,41 +1,8 @@
import { YDoc } from '@/application/collab.type'; import { YDoc } from '@/application/collab.type';
import { getAuthInfo } from '@/application/services/js-services/storage';
import * as Y from 'yjs';
import { IndexeddbPersistence } from 'y-indexeddb';
import { databasePrefix } from '@/application/constants'; import { databasePrefix } from '@/application/constants';
import BaseDexie from 'dexie'; import { getAuthInfo } from '@/application/services/js-services/storage';
import { usersSchema, UsersTable } from './tables/users'; import { IndexeddbPersistence } from 'y-indexeddb';
import * as Y from 'yjs';
const version = 1;
type DexieTables = UsersTable;
export type Dexie<T = DexieTables> = BaseDexie & T;
let db: Dexie | undefined;
export function getDB() {
const authInfo = getAuthInfo();
if (!db && authInfo?.uuid) {
return openDB(authInfo?.uuid);
}
return db;
}
export function openDB(uuid: string) {
const dbName = `${databasePrefix}_${uuid}`;
if (db && db.name === dbName) {
return db;
}
db = new BaseDexie(dbName) as Dexie;
const schema = Object.assign({}, usersSchema);
db.version(version).stores(schema);
return db;
}
/** /**
* Open the collaboration database, and return a function to close it * Open the collaboration database, and return a function to close it
@ -66,3 +33,10 @@ export async function deleteCollabDB(docName: string) {
await provider.destroy(); await provider.destroy();
} }
export function getDBName(id: string, type: string) {
const { uuid } = getAuthInfo() || {};
if (!uuid) throw new Error('No user found');
return `${uuid}_${type}_${id}`;
}

View File

@ -1,10 +0,0 @@
import { Table } from 'dexie';
import { UserProfile } from '@/application/user.type';
export type UsersTable = {
users: Table<UserProfile>;
};
export const usersSchema = {
users: 'uuid, uid, email, name, workspaceId, iconUrl',
};

View File

@ -1,41 +1,20 @@
import { CollabOrigin, CollabType, YDoc } from '@/application/collab.type'; import { CollabOrigin, CollabType, YDoc } from '@/application/collab.type';
import { getDocumentStorage } from '@/application/services/js-services/storage/document'; import { getCollabStorageWithAPICall } from '@/application/services/js-services/storage';
import { DocumentService } from '@/application/services/services.type'; import { DocumentService } from '@/application/services/services.type';
import { APIService } from 'src/application/services/js-services/wasm';
import { applyDocument } from 'src/application/ydoc/apply';
export class JSDocumentService implements DocumentService { export class JSDocumentService implements DocumentService {
constructor() { constructor() {
// //
} }
fetchDocument(workspaceId: string, docId: string) {
return APIService.getCollab(workspaceId, docId, CollabType.Document);
}
async openDocument(workspaceId: string, docId: string): Promise<YDoc> { async openDocument(workspaceId: string, docId: string): Promise<YDoc> {
const { doc, localExist } = await getDocumentStorage(docId); const doc = await getCollabStorageWithAPICall(workspaceId, docId, CollabType.Document);
const asyncApply = async () => {
const res = await this.fetchDocument(workspaceId, docId);
applyDocument(doc, res.state);
};
// If the document exists locally, apply the state asynchronously,
// otherwise, apply the state synchronously
if (localExist) {
void asyncApply();
} else {
await asyncApply();
}
const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => { const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => {
if (origin === CollabOrigin.Remote) { if (origin === CollabOrigin.LocalSync) {
return; // Send the update to the server
console.log('update', update);
} }
// Send the update to the server
console.log('update', update);
}; };
doc.on('update', handleUpdate); doc.on('update', handleUpdate);

View File

@ -1,41 +1,19 @@
import { CollabOrigin, CollabType, YDoc } from '@/application/collab.type'; import { CollabOrigin, CollabType, YDoc } from '@/application/collab.type';
import { getFolderStorage } from '@/application/services/js-services/storage/folder'; import { getCollabStorageWithAPICall } from '@/application/services/js-services/storage';
import { FolderService } from '@/application/services/services.type'; import { FolderService } from '@/application/services/services.type';
import { APIService } from 'src/application/services/js-services/wasm';
import { applyDocument } from 'src/application/ydoc/apply';
export class JSFolderService implements FolderService { export class JSFolderService implements FolderService {
constructor() { constructor() {
// //
} }
fetchFolder(workspaceId: string) {
return APIService.getCollab(workspaceId, workspaceId, CollabType.Folder);
}
async openWorkspace(workspaceId: string): Promise<YDoc> { async openWorkspace(workspaceId: string): Promise<YDoc> {
const { doc, localExist } = await getFolderStorage(workspaceId); const doc = await getCollabStorageWithAPICall(workspaceId, workspaceId, CollabType.Folder);
const asyncApply = async () => {
const res = await this.fetchFolder(workspaceId);
applyDocument(doc, res.state);
};
// If the document exists locally, apply the state asynchronously,
// otherwise, apply the state synchronously
if (localExist) {
void asyncApply();
} else {
await asyncApply();
}
const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => { const handleUpdate = (update: Uint8Array, origin: CollabOrigin) => {
if (origin === CollabOrigin.Remote) { if (origin === CollabOrigin.LocalSync) {
return; // Send the update to the server
console.log('update', update);
} }
// Send the update to the server
console.log('update', update);
}; };
doc.on('update', handleUpdate); doc.on('update', handleUpdate);

View File

@ -1,7 +1,9 @@
import { JSDatabaseService } from '@/application/services/js-services/database.service';
import { import {
AFService, AFService,
AFServiceConfig, AFServiceConfig,
AuthService, AuthService,
DatabaseService,
DocumentService, DocumentService,
FolderService, FolderService,
UserService, UserService,
@ -22,6 +24,8 @@ export class AFClientService implements AFService {
folderService: FolderService; folderService: FolderService;
databaseService: DatabaseService;
private deviceId: string = nanoid(8); private deviceId: string = nanoid(8);
private clientId: string = 'web'; private clientId: string = 'web';
@ -45,5 +49,6 @@ export class AFClientService implements AFService {
this.userService = new JSUserService(); this.userService = new JSUserService();
this.documentService = new JSDocumentService(); this.documentService = new JSDocumentService();
this.folderService = new JSFolderService(); this.folderService = new JSFolderService();
this.databaseService = new JSDatabaseService();
} }
} }

View File

@ -1,11 +1,3 @@
import { getAuthInfo } from '@/application/services/js-services/storage/token';
import { openDB } from '@/application/services/js-services/db';
export async function signInSuccess() { export async function signInSuccess() {
const authInfo = getAuthInfo(); // Do nothing
if (authInfo) {
// Open the database
openDB(authInfo.uuid);
}
} }

View File

@ -0,0 +1,101 @@
import { CollabType, YDoc, YjsEditorKey } from '@/application/collab.type';
import { getDBName, openCollabDB } from '@/application/services/js-services/db';
import { APIService } from '@/application/services/js-services/wasm';
import { applyDocument } from '@/application/ydoc/apply';
export function fetchCollab(workspaceId: string, id: string, type: CollabType) {
return APIService.getCollab(workspaceId, id, type);
}
export function batchFetchCollab(workspaceId: string, params: { object_id: string; collab_type: CollabType }[]) {
return APIService.batchGetCollab(workspaceId, params);
}
function collabTypeToDBType(type: CollabType) {
switch (type) {
case CollabType.Folder:
return 'folder';
case CollabType.Document:
return 'document';
case CollabType.Database:
return 'database';
case CollabType.WorkspaceDatabase:
return 'databases';
case CollabType.DatabaseRow:
return 'database_row';
case CollabType.UserAwareness:
return 'user_awareness';
default:
return '';
}
}
export async function getCollabStorage(id: string, type: CollabType) {
const name = getDBName(id, collabTypeToDBType(type));
const doc = await openCollabDB(name);
const localExist = doc.share.has(YjsEditorKey.data_section);
return {
doc,
localExist,
};
}
export async function getCollabStorageWithAPICall(workspaceId: string, id: string, type: CollabType) {
const { doc, localExist } = await getCollabStorage(id, type);
const asyncApply = async () => {
const res = await fetchCollab(workspaceId, id, type);
applyDocument(doc, res.state);
};
// If the document exists locally, apply the state asynchronously,
// otherwise, apply the state synchronously
if (localExist) {
void asyncApply();
} else {
await asyncApply();
}
return doc;
}
export async function batchCollabs(
workspaceId: string,
params: {
object_id: string;
collab_type: CollabType;
}[],
rowCallback?: (id: string, doc: YDoc) => void
) {
console.log('Fetching collab data:', params);
// Create or get Y.Doc from local storage
for (const item of params) {
const { object_id, collab_type } = item;
const { doc } = await getCollabStorage(object_id, collab_type);
if (rowCallback) {
rowCallback(object_id, doc);
}
}
// Async fetch collab data and apply to Y.Doc
void (async () => {
const res = await batchFetchCollab(workspaceId, params);
for (const id of Object.keys(res)) {
const type = params.find((param) => param.object_id === id)?.collab_type;
const data = res[id];
if (type === undefined || !data) {
continue;
}
const { doc } = await getCollabStorage(id, type);
applyDocument(doc, data);
}
})();
}

View File

@ -1,21 +0,0 @@
import { YjsEditorKey } from '@/application/collab.type';
import { openCollabDB } from '@/application/services/js-services/db';
import { getAuthInfo } from '@/application/services/js-services/storage/token';
export async function getDocumentStorage(docId: string) {
const docName = getDocName(docId);
const doc = await openCollabDB(docName);
const localExist = doc.share.has(YjsEditorKey.data_section);
return {
doc,
localExist,
};
}
export function getDocName(docId: string) {
const { uuid } = getAuthInfo() || {};
if (!uuid) throw new Error('No user found');
return `${uuid}_document_${docId}`;
}

View File

@ -1,21 +0,0 @@
import { YjsEditorKey } from '@/application/collab.type';
import { openCollabDB } from '@/application/services/js-services/db';
import { getAuthInfo } from '@/application/services/js-services/storage/token';
export async function getFolderStorage(workspaceId: string) {
const docName = getDocName(workspaceId);
const doc = await openCollabDB(docName);
const localExist = doc.share.has(YjsEditorKey.data_section);
return {
doc,
localExist,
};
}
export function getDocName(workspaceId: string) {
const { uuid } = getAuthInfo() || {};
if (!uuid) throw new Error('No user found');
return `${uuid}_folder_${workspaceId}`;
}

View File

@ -1,2 +1,4 @@
export * from './token'; export * from './token';
export * from './user'; export * from './user';
export * from './collab';
export * from './auth';

View File

@ -1,18 +1,36 @@
import { UserProfile } from '@/application/user.type'; import { UserProfile, UserWorkspace } from '@/application/user.type';
import { getDB } from '@/application/services/js-services/db';
import { getAuthInfo } from '@/application/services/js-services/storage/token';
const primaryKeyName = 'uid'; const userKey = 'user';
const workspaceKey = 'workspace';
export async function getSignInUser(): Promise<UserProfile | undefined> { export async function getSignInUser(): Promise<UserProfile | undefined> {
const db = getDB(); const userStr = localStorage.getItem(userKey);
const authInfo = getAuthInfo();
return db?.users.get(authInfo?.uuid); try {
return userStr ? JSON.parse(userStr) : undefined;
} catch (e) {
return undefined;
}
} }
export async function setSignInUser(profile: UserProfile) { export async function setSignInUser(profile: UserProfile) {
const db = getDB(); const userStr = JSON.stringify(profile);
return db?.users.put(profile, primaryKeyName); localStorage.setItem(userKey, userStr);
}
export async function getUserWorkspace(): Promise<UserWorkspace | undefined> {
const str = localStorage.getItem(workspaceKey);
try {
return str ? JSON.parse(str) : undefined;
} catch (e) {
return undefined;
}
}
export async function setUserWorkspace(workspace: UserWorkspace) {
const str = JSON.stringify(workspace);
localStorage.setItem(workspaceKey, str);
} }

View File

@ -1,7 +1,14 @@
import { UserService } from '@/application/services/services.type'; import { UserService } from '@/application/services/services.type';
import { UserProfile } from '@/application/user.type'; import { UserProfile, UserWorkspace } from '@/application/user.type';
import { APIService } from 'src/application/services/js-services/wasm'; import { APIService } from 'src/application/services/js-services/wasm';
import { getAuthInfo, getSignInUser, invalidToken, setSignInUser } from '@/application/services/js-services/storage'; import {
getAuthInfo,
getSignInUser,
getUserWorkspace,
invalidToken,
setSignInUser,
setUserWorkspace,
} from '@/application/services/js-services/storage';
import { asyncDataDecorator } from '@/application/services/js-services/decorator'; import { asyncDataDecorator } from '@/application/services/js-services/decorator';
async function getUser() { async function getUser() {
@ -22,10 +29,17 @@ export class JSUserService implements UserService {
return Promise.reject('Not authenticated'); return Promise.reject('Not authenticated');
} }
await this.getUserWorkspace();
return null!; return null!;
} }
async checkUser(): Promise<boolean> { async checkUser(): Promise<boolean> {
return (await getSignInUser()) !== undefined; return (await getSignInUser()) !== undefined;
} }
@asyncDataDecorator<void, UserWorkspace>(getUserWorkspace, setUserWorkspace, APIService.getUserWorkspace)
async getUserWorkspace(): Promise<UserWorkspace> {
return null!;
}
} }

View File

@ -1,6 +1,6 @@
import { CollabType } from '@/application/collab.type'; import { CollabType } from '@/application/collab.type';
import { ClientAPI } from '@appflowyinc/client-api-wasm'; import { ClientAPI } from '@appflowyinc/client-api-wasm';
import { UserProfile } from '@/application/user.type'; import { UserProfile, UserWorkspace } from '@/application/user.type';
import { AFCloudConfig } from '@/application/services/services.type'; import { AFCloudConfig } from '@/application/services/services.type';
import { invalidToken, readTokenStr, writeToken } from '@/application/services/js-services/storage'; import { invalidToken, readTokenStr, writeToken } from '@/application/services/js-services/storage';
@ -77,3 +77,45 @@ export async function getCollab(workspaceId: string, object_id: string, collabTy
state, state,
}; };
} }
export async function batchGetCollab(
workspaceId: string,
params: {
object_id: string;
collab_type: CollabType;
}[]
) {
const res = (await client.batch_get_collab(
workspaceId,
params.map((param) => ({
object_id: param.object_id,
collab_type: Number(param.collab_type) as 0 | 1 | 2 | 3 | 4 | 5,
}))
)) as unknown as Map<string, { doc_state: number[] }>;
const result: Record<string, Uint8Array> = {};
res.forEach((value, key) => {
result[key] = new Uint8Array(value.doc_state);
});
return result;
}
export async function getUserWorkspace(): Promise<UserWorkspace> {
const res = await client.get_user_workspace();
return {
visitingWorkspaceId: res.visiting_workspace_id,
workspaces: res.workspaces.map((workspace) => ({
id: workspace.workspace_id,
name: workspace.workspace_name,
icon: workspace.icon,
owner: {
id: Number(workspace.owner_uid),
name: workspace.owner_name,
},
type: workspace.workspace_type,
workspaceDatabaseId: workspace.database_storage_id,
})),
};
}

View File

@ -1,5 +1,6 @@
import { YDoc } from '@/application/collab.type'; import { YDoc } from '@/application/collab.type';
import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/user.type'; import { ProviderType, SignUpWithEmailPasswordParams, UserProfile } from '@/application/user.type';
import * as Y from 'yjs';
export interface AFService { export interface AFService {
getDeviceID: () => string; getDeviceID: () => string;
@ -8,6 +9,7 @@ export interface AFService {
userService: UserService; userService: UserService;
documentService: DocumentService; documentService: DocumentService;
folderService: FolderService; folderService: FolderService;
databaseService: DatabaseService;
} }
export interface AFServiceConfig { export interface AFServiceConfig {
@ -32,6 +34,23 @@ export interface DocumentService {
openDocument: (workspaceId: string, docId: string) => Promise<YDoc>; openDocument: (workspaceId: string, docId: string) => Promise<YDoc>;
} }
export interface DatabaseService {
openDatabase: (
workspaceId: string,
viewId: string
) => Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}>;
getDatabase: (
workspaceId: string,
databaseId: string
) => Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}>;
}
export interface UserService { export interface UserService {
getUserProfile: () => Promise<UserProfile | null>; getUserProfile: () => Promise<UserProfile | null>;
checkUser: () => Promise<boolean>; checkUser: () => Promise<boolean>;

View File

@ -0,0 +1,29 @@
import { YDoc } from '@/application/collab.type';
import { DatabaseService } from '@/application/services/services.type';
import * as Y from 'yjs';
export class TauriDatabaseService implements DatabaseService {
constructor() {
//
}
async openDatabase(
_workspaceId: string,
_viewId: string
): Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}> {
return Promise.reject('Not implemented');
}
async getDatabase(
_workspaceId: string,
_databaseId: string
): Promise<{
databaseDoc: YDoc;
rows: Y.Map<YDoc>;
}> {
return Promise.reject('Not implemented');
}
}

View File

@ -1,5 +1,5 @@
import { DocumentService } from '@/application/services/services.type'; import { DocumentService } from '@/application/services/services.type';
import Y from 'yjs'; import * as Y from 'yjs';
export class TauriDocumentService implements DocumentService { export class TauriDocumentService implements DocumentService {
async openDocument(_id: string): Promise<Y.Doc> { async openDocument(_id: string): Promise<Y.Doc> {

View File

@ -2,11 +2,13 @@ import {
AFService, AFService,
AFServiceConfig, AFServiceConfig,
AuthService, AuthService,
DatabaseService,
DocumentService, DocumentService,
FolderService, FolderService,
UserService, UserService,
} from '@/application/services/services.type'; } from '@/application/services/services.type';
import { TauriAuthService } from '@/application/services/tauri-services/auth.service'; import { TauriAuthService } from '@/application/services/tauri-services/auth.service';
import { TauriDatabaseService } from '@/application/services/tauri-services/database.service';
import { TauriFolderService } from '@/application/services/tauri-services/folder.service'; import { TauriFolderService } from '@/application/services/tauri-services/folder.service';
import { TauriUserService } from '@/application/services/tauri-services/user.service'; import { TauriUserService } from '@/application/services/tauri-services/user.service';
import { TauriDocumentService } from '@/application/services/tauri-services/document.service'; import { TauriDocumentService } from '@/application/services/tauri-services/document.service';
@ -21,6 +23,8 @@ export class AFClientService implements AFService {
folderService: FolderService; folderService: FolderService;
databaseService: DatabaseService;
private deviceId: string = nanoid(8); private deviceId: string = nanoid(8);
private clientId: string = 'web'; private clientId: string = 'web';
@ -41,5 +45,6 @@ export class AFClientService implements AFService {
this.userService = new TauriUserService(); this.userService = new TauriUserService();
this.documentService = new TauriDocumentService(); this.documentService = new TauriDocumentService();
this.folderService = new TauriFolderService(); this.folderService = new TauriFolderService();
this.databaseService = new TauriDatabaseService();
} }
} }

View File

@ -54,7 +54,11 @@ export const YjsEditor = {
}, },
}; };
export function withYjs<T extends Editor>(editor: T, doc: Y.Doc): T & YjsEditor { export function withYjs<T extends Editor>(
editor: T,
doc: Y.Doc,
localOrigin: CollabOrigin = CollabOrigin.Local
): T & YjsEditor {
const e = editor as T & YjsEditor; const e = editor as T & YjsEditor;
const { apply, onChange } = e; const { apply, onChange } = e;
@ -73,11 +77,9 @@ export function withYjs<T extends Editor>(editor: T, doc: Y.Doc): T & YjsEditor
}; };
const handleYEvents = (events: Array<YEvent<YSharedRoot>>, transaction: Transaction) => { const handleYEvents = (events: Array<YEvent<YSharedRoot>>, transaction: Transaction) => {
if (transaction.origin === CollabOrigin.Local) { if (transaction.origin === CollabOrigin.Remote) {
return; YjsEditor.applyRemoteEvents(e, events, transaction);
} }
YjsEditor.applyRemoteEvents(e, events, transaction);
}; };
e.connect = () => { e.connect = () => {
@ -123,7 +125,7 @@ export function withYjs<T extends Editor>(editor: T, doc: Y.Doc): T & YjsEditor
changes.forEach((change) => { changes.forEach((change) => {
applySlateOp(doc, { children: change.slateContent }, change.op); applySlateOp(doc, { children: change.slateContent }, change.op);
}); });
}, CollabOrigin.Local); }, localOrigin);
}; };
e.apply = (op) => { e.apply = (op) => {

View File

@ -1,6 +1,6 @@
import { Operation, Node } from 'slate'; import { Operation, Node } from 'slate';
import Y from 'yjs'; import * as Y from 'yjs';
export function applySlateOp (ydoc: Y.Doc, slateRoot: Node, op: Operation) { export function applySlateOp(ydoc: Y.Doc, slateRoot: Node, op: Operation) {
console.log('applySlateOp', op); console.log('applySlateOp', op);
} }

View File

@ -1,4 +1,4 @@
import { YSharedRoot } from '@/application/document.type'; import { YSharedRoot } from '@/application/collab.type';
import * as Y from 'yjs'; import * as Y from 'yjs';
import { Editor, Operation } from 'slate'; import { Editor, Operation } from 'slate';

View File

@ -18,6 +18,11 @@ export interface UserProfile {
workspaceId?: string; workspaceId?: string;
} }
export interface UserWorkspace {
visitingWorkspaceId: string;
workspaces: Workspace[];
}
export interface Workspace { export interface Workspace {
id: string; id: string;
name: string; name: string;
@ -26,6 +31,8 @@ export interface Workspace {
id: number; id: number;
name: string; name: string;
}; };
type: number;
workspaceDatabaseId: string;
} }
export interface SignUpWithEmailPasswordParams { export interface SignUpWithEmailPasswordParams {

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 4C7.72386 4 7.5 4.22386 7.5 4.5V7.5H4.5C4.22386 7.5 4 7.72386 4 8C4 8.27614 4.22386 8.5 4.5 8.5H7.5V11.5C7.5 11.7761 7.72386 12 8 12C8.27614 12 8.5 11.7761 8.5 11.5V8.5H11.5C11.7761 8.5 12 8.27614 12 8C12 7.72386 11.7761 7.5 11.5 7.5H8.5V4.5C8.5 4.22386 8.27614 4 8 4Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 402 B

View File

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4L12 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 8H11" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12L12 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 361 B

View File

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4L12 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 8H10" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12L12 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 361 B

View File

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4L12 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8H12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 12L12 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 361 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 4L6 8L10 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 195 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 4L10 8L6 12" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 194 B

View File

@ -1,16 +0,0 @@
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M12.8 2H3.2C2.53726 2 2 2.55964 2 3.25V5.75C2 6.44036 2.53726 7 3.2 7H12.8C13.4627 7 14 6.44036 14 5.75V3.25C14 2.55964 13.4627 2 12.8 2Z'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
/>
<path
d='M12.8 9H3.2C2.53726 9 2 9.55964 2 10.25V12.75C2 13.4404 2.53726 14 3.2 14H12.8C13.4627 14 14 13.4404 14 12.75V10.25C14 9.55964 13.4627 9 12.8 9Z'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
/>
<circle cx='4.5' cy='4.5' r='0.5' fill='currentColor' />
<circle cx='4.5' cy='11.5' r='0.5' fill='currentColor' />
</svg>

Before

Width:  |  Height:  |  Size: 788 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 8C9.66667 8 11 8.4 11 10C11 11.6 9.66667 12 9 12H6V8M9 8H6M9 8C9.5 8 10.5171 6.97616 10.5 6C10.4806 4.8956 9.5 4 8.5 4H6V8" stroke="#333333" stroke-width="1.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 277 B

View File

@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5V8L10 9" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.5 2.5L13.5 4.5" stroke="#333333" stroke-linecap="round"/>
<path d="M4.5 2.5L2.5 4.5" stroke="#333333" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 516 B

View File

@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="15.7124" y="7.22656" width="1.5" height="12" rx="0.75" transform="rotate(45 15.7124 7.22656)" fill="#333333"/>
<rect x="16.7729" y="15.7109" width="1.5" height="12" rx="0.75" transform="rotate(135 16.7729 15.7109)" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 344 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.9743 6.33203H7.35889C6.79245 6.33203 6.33325 6.79123 6.33325 7.35767V11.9731C6.33325 12.5395 6.79245 12.9987 7.35889 12.9987H11.9743C12.5407 12.9987 12.9999 12.5395 12.9999 11.9731V7.35767C12.9999 6.79123 12.5407 6.33203 11.9743 6.33203Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.53846 9.66667H4.02564C3.75362 9.66667 3.49275 9.55861 3.3004 9.36626C3.10806 9.17392 3 8.91304 3 8.64103V4.02564C3 3.75362 3.10806 3.49275 3.3004 3.3004C3.49275 3.10806 3.75362 3 4.02564 3H8.64103C8.91304 3 9.17392 3.10806 9.36626 3.3004C9.55861 3.49275 9.66667 3.75362 9.66667 4.02564V4.53846" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 794 B

View File

@ -1,73 +0,0 @@
<svg width='103' height='24' viewBox='0 0 103 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g clip-path='url(#clip0_175_6957)'>
<path
d='M34.7324 7.84144H36.892V8.63601C37.4179 8.08166 38.4132 7.63818 39.5212 7.63818C41.8498 7.63818 43.3709 9.4121 43.3709 11.8882C43.3709 14.4197 41.6057 16.3969 38.8827 16.3969C38.1315 16.3969 37.3991 16.2675 36.9108 15.9904V19.9078H34.7512V7.84144H34.7324ZM36.9108 10.5393V13.9208C37.493 14.2904 37.9812 14.4012 38.6949 14.4012C40.1972 14.4012 41.0611 13.3295 41.0611 11.9436C41.0611 10.6132 40.2723 9.65231 38.8451 9.65231C38.1127 9.63384 37.4366 9.96644 36.9108 10.5393Z'
fill='white'
/>
<path
d='M29.3802 7.6748C26.6572 7.6748 24.892 9.6335 24.892 12.1835C24.892 14.6596 26.3943 16.4335 28.7417 16.4335C29.8309 16.4335 30.8262 15.99 31.3708 15.4357V16.2302H33.5305V9.22698C31.9154 8.02589 30.1314 7.6748 29.3802 7.6748ZM31.3521 13.5324C30.8262 14.1052 30.1502 14.4378 29.4178 14.4378C27.9906 14.4378 27.2018 13.477 27.2018 12.1465C27.2018 10.7607 28.0657 9.67046 29.568 9.67046C30.2816 9.67046 30.7699 9.78133 31.3521 10.1509V13.5324Z'
fill='white'
/>
<path
d='M44.291 7.84144H46.4506V8.63601C46.9765 8.08166 47.9718 7.63818 49.0797 7.63818C51.4084 7.63818 52.9295 9.4121 52.9295 11.8882C52.9295 14.4197 51.1642 16.3969 48.4412 16.3969C47.6901 16.3969 46.9577 16.2675 46.4694 15.9904V19.9078H44.3098V7.84144H44.291ZM46.4694 10.5393V13.9208C47.0516 14.2904 47.5398 14.4012 48.2535 14.4012C49.7558 14.4012 50.6196 13.3295 50.6196 11.9436C50.6196 10.6132 49.8309 9.65231 48.4037 9.65231C47.6713 9.63384 46.9952 9.96644 46.4694 10.5393Z'
fill='white'
/>
<path
d='M59.2019 5.43925C58.8826 5.21751 58.3756 4.99577 57.8685 4.99577C56.892 4.99577 56.3849 5.49469 56.3849 6.64034V7.82295H57.9061V9.83708H56.3849V16.1751H54.2065V6.49251C54.2065 4.18273 55.3333 3.00012 57.23 3.00012C58.0939 3.00012 58.8826 3.20338 59.446 3.55447H61.3615V13.0153C61.3615 13.9577 61.6056 14.3643 62.0939 14.3643C62.4507 14.3643 62.7324 14.2164 62.9953 14.0316L63.446 15.6577C62.9577 16.0458 62.2253 16.3599 61.2112 16.3599C59.9155 16.3599 59.2019 15.5838 59.2019 13.8838V5.43925Z'
fill='white'
/>
<path
d='M68.0846 7.63818C70.9015 7.63818 72.7043 9.50449 72.7043 12.0175C72.7043 14.5121 70.9015 16.3969 68.0846 16.3969C65.2677 16.3969 63.4648 14.5306 63.4648 12.0175C63.4648 9.50449 65.2864 7.63818 68.0846 7.63818ZM68.0846 14.4012C69.4179 14.4012 70.3944 13.4588 70.3944 12.0175C70.3944 10.6132 69.3991 9.65231 68.0846 9.65231C66.8076 9.65231 65.7935 10.5762 65.7935 12.0175C65.7935 13.4034 66.77 14.4012 68.0846 14.4012Z'
fill='white'
/>
<path
d='M83.4274 16.2308H81.4931L79.6903 11.5928C79.5588 11.2786 79.5025 10.9275 79.4086 10.6134C79.3335 10.983 79.2396 11.2971 79.1081 11.6297L77.3053 16.2308H75.4649L72.7043 7.84167H75.0142L76.3663 12.3873C76.4978 12.7938 76.5353 13.108 76.6105 13.496C76.7043 13.1449 76.7607 12.8493 76.9297 12.3873L78.5072 7.84167H80.4227L82.0565 12.3688C82.1879 12.7384 82.2818 13.1265 82.3757 13.5145C82.4508 13.1265 82.5447 12.7199 82.6386 12.3134L83.8781 7.84167H86.0565L83.4274 16.2308Z'
fill='white'
/>
<path
d='M90.6573 16.1936C89.4178 19.187 88.6291 19.9077 87.3709 19.9077C86.6009 19.9077 86.0375 19.612 85.5493 19.2609L86.2441 17.6533C86.4695 17.7827 86.7887 17.949 87.1267 17.949C87.6713 17.949 88.0657 17.524 88.4225 16.6555L88.6103 16.212L84.6854 7.85986H87.2958L89.2676 12.424C89.4366 12.8305 89.5493 13.2186 89.6807 13.6251C89.7558 13.237 89.831 12.8305 89.9812 12.4055L91.5023 7.85986H93.9624L90.6573 16.1936Z'
fill='white'
/>
<path
d='M19.0141 13.4033C18.5258 16.0088 16.385 18.2816 13.9249 19.5566C13.6244 19.7229 13.2676 19.8153 12.9296 19.8338H18.1502C18.6573 19.8338 19.0141 19.4642 19.0141 19.0022V13.4033Z'
fill='#F7931E'
/>
<path
d='M8.57282 8.37731C8.49771 8.43274 8.42259 8.48817 8.34747 8.54361C7.07048 9.43057 3.20193 12.2762 2.3193 11.0382C1.45545 9.83709 2.37564 6.43709 4.5916 4.79252C4.62916 4.75557 4.6855 4.73709 4.72306 4.70013C7.14559 3.01861 8.94841 3.25883 9.83104 4.47839C10.6573 5.62404 9.73714 7.43491 8.57282 8.37731Z'
fill='#8427E0'
/>
<path
d='M18.0376 11.0204C16.8357 11.852 14.9578 10.8911 14.0188 9.69C13.9812 9.63456 13.9437 9.59761 13.9061 9.54217C13.0047 8.28565 10.1127 4.47913 11.3521 3.62913C12.5916 2.76065 16.1784 3.70304 17.8122 5.99434C17.8498 6.04978 17.8873 6.08674 17.9249 6.14217C19.5024 8.45195 19.2582 10.1704 18.0376 11.0204Z'
fill='#00B5FF'
/>
<path
d='M16.4226 18.5219C16.385 18.5589 16.3475 18.5773 16.2911 18.6143C13.8686 20.2958 12.0658 20.0556 11.1832 18.836C10.3569 17.6904 11.277 15.8795 12.4414 14.9371C12.5165 14.8817 12.5916 14.8263 12.6667 14.7708C13.9437 13.9023 17.8123 11.0382 18.6761 12.2763C19.5587 13.4773 18.6573 16.8773 16.4226 18.5219Z'
fill='#FFBD00'
/>
<path
d='M9.66194 19.6861C8.4225 20.5545 4.85443 19.6121 3.22063 17.3208C3.18307 17.2839 3.14551 17.2285 3.12673 17.1915C1.53049 14.8817 1.7934 13.1448 3.01405 12.3132C4.21593 11.4817 6.09387 12.4426 7.03283 13.6437C7.07039 13.6991 7.10795 13.7361 7.14551 13.7915C8.02814 15.0295 10.9202 18.8361 9.66194 19.6861Z'
fill='#E3006D'
/>
<path
d='M8.57283 8.37731C6.84513 9.13491 3.16438 10.6871 2.61978 9.43057C2.1503 8.37731 3.01415 6.23383 4.59161 4.79252C4.62917 4.75557 4.68551 4.73709 4.72307 4.70013C7.1456 3.01861 8.94842 3.25883 9.83105 4.47839C10.6573 5.62404 9.73715 7.43491 8.57283 8.37731Z'
fill='#9327FF'
/>
<path
d='M18.0375 11.0196C16.8357 11.8511 14.9577 10.8902 14.0188 9.68912C13.2488 7.93368 11.7652 4.47825 13.0047 3.96086C14.1314 3.48042 16.4601 4.44129 17.9436 6.14129C19.5023 8.45107 19.2582 10.1696 18.0375 11.0196Z'
fill='#00C8FF'
/>
<path
d='M16.4226 18.5218C16.385 18.5587 16.3475 18.5772 16.2911 18.6142C13.8686 20.2957 12.0658 20.0555 11.1832 18.8359C10.3569 17.6903 11.277 15.8794 12.4414 14.937C14.1691 14.1794 17.8498 12.6272 18.3944 13.8837C18.8827 14.937 18.0188 17.0805 16.4226 18.5218Z'
fill='#F7CF46'
/>
<path
d='M8.04692 19.3535C6.92016 19.8339 4.59152 18.8915 3.12673 17.1915C1.53049 14.8817 1.7934 13.1448 3.01405 12.3132C4.21593 11.4817 6.09387 12.4426 7.03283 13.6437C7.80279 15.3806 9.28635 18.8361 8.04692 19.3535Z'
fill='#FB006D'
/>
</g>
<defs>
<clipPath id='clip0_175_6957'>
<rect width='92' height='17' fill='white' transform='translate(2 3)' />
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="3" width="13" height="13" rx="3.5" fill="currentColor"/>
<path d="M6 9L8 12L12 7" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 272 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.5" y="3" width="12" height="12" rx="3.5" stroke="#BDBDBD"/>
</svg>

Before

Width:  |  Height:  |  Size: 178 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 7.688L8.27223 12.1469C7.69304 12.6931 6.90749 13 6.0884 13C5.26931 13 4.48376 12.6931 3.90457 12.1469C3.32538 11.6006 3 10.8598 3 10.0873C3 9.31474 3.32538 8.57387 3.90457 8.02763L8.63234 3.56875C9.01847 3.20459 9.54216 3 10.0882 3C10.6343 3 11.158 3.20459 11.5441 3.56875C11.9302 3.93291 12.1472 4.42683 12.1472 4.94183C12.1472 5.45684 11.9302 5.95075 11.5441 6.31491L6.8112 10.7738C6.61814 10.9559 6.35629 11.0582 6.08326 11.0582C5.81022 11.0582 5.54838 10.9559 5.35531 10.7738C5.16225 10.5917 5.05379 10.3448 5.05379 10.0873C5.05379 9.82975 5.16225 9.58279 5.35531 9.40071L9.72297 5.28632" stroke="#333333" stroke-width="0.9989" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 797 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 8L8.11538 9.5L13.5 4.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 8.5V11.8889C13 12.1836 12.8829 12.4662 12.6746 12.6746C12.4662 12.8829 12.1836 13 11.8889 13H4.11111C3.81643 13 3.53381 12.8829 3.32544 12.6746C3.11706 12.4662 3 12.1836 3 11.8889V4.11111C3 3.81643 3.11706 3.53381 3.32544 3.32544C3.53381 3.11706 3.81643 3 4.11111 3H10.2222" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 561 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 8L8.11538 9.5L13.5 4.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 8C13.5 11.0376 11.0376 13.5 8 13.5C4.96243 13.5 2.5 11.0376 2.5 8C2.5 4.96243 4.96243 2.5 8 2.5C8.81896 2.5 9.59612 2.679 10.2945 3" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 421 B

View File

@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8889 3.5H4.11111C3.49746 3.5 3 3.94772 3 4.5V11.5C3 12.0523 3.49746 12.5 4.11111 12.5H11.8889C12.5025 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5025 3.5 11.8889 3.5Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 6.5H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 618 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 5V8L10 9" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 376 B

View File

@ -1,8 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 4L12.5 4" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.5 8H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.5 12H12.5" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="4" cy="4" r="0.5" fill="#333333"/>
<circle cx="4" cy="8" r="0.5" fill="#333333"/>
<circle cx="4" cy="12" r="0.5" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 512 B

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.201 6.4H3.001V12H2.081V7.384L0.953 7.704L0.729 6.92L2.201 6.4ZM3.91156 12V11.1L6.35156 8.61C6.9449 8.01667 7.24156 7.50333 7.24156 7.07C7.24156 6.73 7.13823 6.46667 6.93156 6.28C6.73156 6.08667 6.4749 5.99 6.16156 5.99C5.5749 5.99 5.14156 6.28 4.86156 6.86L3.89156 6.29C4.11156 5.82333 4.42156 5.47 4.82156 5.23C5.22156 4.99 5.6649 4.87 6.15156 4.87C6.7649 4.87 7.29156 5.06333 7.73156 5.45C8.17156 5.83667 8.39156 6.36333 8.39156 7.03C8.39156 7.74333 7.9949 8.50333 7.20156 9.31L5.62156 10.89H8.52156V12H3.91156ZM12.9025 7.032C13.5105 7.176 14.0025 7.46 14.3785 7.884C14.7625 8.3 14.9545 8.824 14.9545 9.456C14.9545 10.296 14.6705 10.956 14.1025 11.436C13.5345 11.916 12.8385 12.156 12.0145 12.156C11.3745 12.156 10.7985 12.008 10.2865 11.712C9.78253 11.416 9.41853 10.984 9.19453 10.416L10.3705 9.732C10.6185 10.452 11.1665 10.812 12.0145 10.812C12.4945 10.812 12.8745 10.692 13.1545 10.452C13.4345 10.204 13.5745 9.872 13.5745 9.456C13.5745 9.04 13.4345 8.712 13.1545 8.472C12.8745 8.232 12.4945 8.112 12.0145 8.112H11.7025L11.1505 7.284L12.9625 4.896H9.44653V3.6H14.6065V4.776L12.9025 7.032Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="5" r="2.5" stroke="#333333"/>
<path d="M3 13C3 10.2386 5.23858 8 8 8C10.7614 8 13 10.2386 13 13" stroke="#333333" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 261 B

View File

@ -1,8 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="11.8274" cy="5.82739" r="1.5" stroke="#333333"/>
<path d="M10.5008 5.38471L6.24097 4.78992" stroke="#333333"/>
<path d="M4.86475 6.24121L6.02777 10.1009" stroke="#333333"/>
<circle cx="7" cy="11" r="1.5" stroke="#333333"/>
<circle cx="5" cy="5" r="1.5" stroke="#333333"/>
<path d="M10.9011 7.14258L8.1484 10.0447" stroke="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 448 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.78787 8.78787L6.51213 7.51213C6.32314 7.32314 6.45699 7 6.72426 7H9.27574C9.54301 7 9.67686 7.32314 9.48787 7.51213L8.21213 8.78787C8.09497 8.90503 7.90503 8.90503 7.78787 8.78787Z" fill="#333333"/>
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 499 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.15625 11.8359L6.43768 9.85414H2.46662L1.74805 11.8359H0.5L3.7903 3H5.11399L8.4043 11.8359H7.15625ZM2.87003 8.75596H6.03427L4.44584 4.40112L2.87003 8.75596Z" fill="#333333"/>
<path d="M14.4032 5.52454H15.5V11.8359H14.4032V10.7504C13.8569 11.5835 13.0627 12 12.0206 12C11.1381 12 10.386 11.6802 9.76403 11.0407C9.14211 10.3927 8.83114 9.60589 8.83114 8.68022C8.83114 7.75456 9.14211 6.97195 9.76403 6.3324C10.386 5.68443 11.1381 5.36045 12.0206 5.36045C13.0627 5.36045 13.8569 5.777 14.4032 6.6101V5.52454ZM12.1593 10.9397C12.798 10.9397 13.3317 10.7251 13.7603 10.2959C14.1889 9.85835 14.4032 9.31978 14.4032 8.68022C14.4032 8.04067 14.1889 7.50631 13.7603 7.07714C13.3317 6.63955 12.798 6.42076 12.1593 6.42076C11.5289 6.42076 10.9995 6.63955 10.5708 7.07714C10.1422 7.50631 9.92791 8.04067 9.92791 8.68022C9.92791 9.31978 10.1422 9.85835 10.5708 10.2959C10.9995 10.7251 11.5289 10.9397 12.1593 10.9397Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 7.688L8.27223 12.1469C7.69304 12.6931 6.90749 13 6.0884 13C5.26931 13 4.48376 12.6931 3.90457 12.1469C3.32538 11.6006 3 10.8598 3 10.0873C3 9.31474 3.32538 8.57387 3.90457 8.02763L8.63234 3.56875C9.01847 3.20459 9.54216 3 10.0882 3C10.6343 3 11.158 3.20459 11.5441 3.56875C11.9302 3.93291 12.1472 4.42683 12.1472 4.94183C12.1472 5.45684 11.9302 5.95075 11.5441 6.31491L6.8112 10.7738C6.61814 10.9559 6.35629 11.0582 6.08326 11.0582C5.81022 11.0582 5.54838 10.9559 5.35531 10.7738C5.16225 10.5917 5.05379 10.3448 5.05379 10.0873C5.05379 9.82975 5.16225 9.58279 5.35531 9.40071L9.72297 5.28632" stroke="#333333" stroke-width="0.9989" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 797 B

View File

@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8889 3.5H4.11111C3.49746 3.5 3 3.94772 3 4.5V11.5C3 12.0523 3.49746 12.5 4.11111 12.5H11.8889C12.5025 12.5 13 12.0523 13 11.5V4.5C13 3.94772 12.5025 3.5 11.8889 3.5Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 2.5V4.58181" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 6.5H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 618 B

View File

@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4.40039H4.11111H13" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.77775 4.4V3.2C5.77775 2.88174 5.89481 2.57652 6.10319 2.35147C6.31156 2.12643 6.59418 2 6.88886 2H9.11108C9.40577 2 9.68838 2.12643 9.89676 2.35147C10.1051 2.57652 10.2222 2.88174 10.2222 3.2V4.4M11.8889 4.4V12.8C11.8889 13.1183 11.7718 13.4235 11.5634 13.6485C11.3551 13.8736 11.0724 14 10.7778 14H5.2222C4.92751 14 4.64489 13.8736 4.43652 13.6485C4.22815 13.4235 4.11108 13.1183 4.11108 12.8V4.4H11.8889Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.88892 7.40039V11.0004" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.11108 7.40039V11.0004" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 889 B

View File

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 8C5 8.55228 4.55228 9 4 9C3.44772 9 3 8.55228 3 8C3 7.44772 3.44772 7 4 7C4.55228 7 5 7.44772 5 8Z" fill="currentColor"/>
<path d="M9 8C9 8.55228 8.55229 9 8 9C7.44772 9 7 8.55228 7 8C7 7.44772 7.44772 7 8 7C8.55229 7 9 7.44772 9 8Z" fill="currentColor"/>
<path d="M12 9C12.5523 9 13 8.55228 13 8C13 7.44772 12.5523 7 12 7C11.4477 7 11 7.44772 11 8C11 8.55228 11.4477 9 12 9Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 514 B

View File

@ -1,14 +0,0 @@
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M10.5 3H11.5C11.8315 3 12.1495 3.12877 12.3839 3.35798C12.6183 3.58719 12.75 3.89807 12.75 4.22222V12.7778C12.75 13.1019 12.6183 13.4128 12.3839 13.642C12.1495 13.8712 11.8315 14 11.5 14H4.5C4.16848 14 3.85054 13.8712 3.61612 13.642C3.3817 13.4128 3.25 13.1019 3.25 12.7778V4.22222C3.25 3.89807 3.3817 3.58719 3.61612 3.35798C3.85054 3.12877 4.16848 3 4.5 3H5.5'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
/>
<path
d='M9.5 2H6.5C6.22386 2 6 2.22386 6 2.5V3.5C6 3.77614 6.22386 4 6.5 4H9.5C9.77614 4 10 3.77614 10 3.5V2.5C10 2.22386 9.77614 2 9.5 2Z'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
/>
</svg>

Before

Width:  |  Height:  |  Size: 875 B

View File

@ -1,8 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="9" y="3" width="2" height="2" rx="0.5" fill="#333333"/>
<rect x="5" y="3" width="2" height="2" rx="0.5" fill="#333333"/>
<rect x="9" y="7" width="2" height="2" rx="0.5" fill="#333333"/>
<rect x="5" y="7" width="2" height="2" rx="0.5" fill="#333333"/>
<rect x="9" y="11" width="2" height="2" rx="0.5" fill="#333333"/>
<rect x="5" y="11" width="2" height="2" rx="0.5" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 495 B

View File

@ -1,6 +0,0 @@
<svg width='100%' height='100%' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M11.5757 14.5757L8.52426 11.5243C8.14629 11.1463 8.41399 10.5 8.94853 10.5H15.0515C15.586 10.5 15.8537 11.1463 15.4757 11.5243L12.4243 14.5757C12.1899 14.8101 11.8101 14.8101 11.5757 14.5757Z'
fill='currentColor'
/>
</svg>

Before

Width:  |  Height:  |  Size: 363 B

View File

@ -1,9 +0,0 @@
<svg width='100%' height='100%' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path d='M8 13H14' stroke='currentColor' strokeLinecap='round' stroke-linejoin='round' />
<path
d='M10.8849 3.36289C11.1173 3.13054 11.4324 3 11.761 3C11.9237 3 12.0848 3.03205 12.2351 3.09431C12.3855 3.15658 12.5221 3.24784 12.6371 3.36289C12.7522 3.47794 12.8434 3.61453 12.9057 3.76485C12.968 3.91517 13 4.07629 13 4.23899C13 4.4017 12.968 4.56281 12.9057 4.71314C12.8434 4.86346 12.7522 5.00004 12.6371 5.11509L5.33627 12.4159L3 13L3.58407 10.6637L10.8849 3.36289Z'
stroke='currentColor'
stroke-linecap='round'
stroke-linejoin='round'
/>
</svg>

Before

Width:  |  Height:  |  Size: 708 B

View File

@ -1,9 +0,0 @@
<svg width='100%' height='100%' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fill-rule='evenodd'
clip-rule='evenodd'
d='M7.68372 17.3771C8.88359 18.1747 10.3278 18.75 12.0001 18.75C15.7261 18.75 18.32 15.8941 19.6011 14.0863C20.4933 12.8272 20.4933 11.1728 19.6011 9.91375C19.1009 9.208 18.4007 8.34252 17.5112 7.54957L16.4489 8.61191C17.236 9.30133 17.8836 10.0844 18.3772 10.781C18.9013 11.5205 18.9013 12.4795 18.3772 13.219C17.1411 14.9633 14.9396 17.25 12.0001 17.25C10.794 17.25 9.71218 16.865 8.77028 16.2905L7.68372 17.3771ZM7.55137 15.3881L6.48903 16.4504C5.5995 15.6575 4.8993 14.792 4.39916 14.0863C3.50692 12.8272 3.50692 11.1728 4.39916 9.91375C5.68028 8.10595 8.27417 5.25 12.0001 5.25C13.6724 5.25 15.1167 5.82531 16.3165 6.62294L15.23 7.7095C14.2881 7.13497 13.2062 6.75 12.0001 6.75C9.06064 6.75 6.85914 9.03672 5.62301 10.781C5.09897 11.5205 5.09897 12.4795 5.62301 13.219C6.11667 13.9156 6.76428 14.6987 7.55137 15.3881ZM10.4887 14.572C10.9279 14.8431 11.4439 15 12.0002 15C13.641 15 14.932 13.6349 14.932 12C14.932 11.4625 14.7925 10.9542 14.5468 10.5139L13.3964 11.6644C13.4197 11.7717 13.432 11.884 13.432 12C13.432 12.8503 12.7694 13.5 12.0002 13.5C11.868 13.5 11.739 13.4808 11.616 13.4448L10.4887 14.572ZM10.6039 12.3355L9.45347 13.486C9.20788 13.0458 9.06836 12.5375 9.06836 12C9.06836 10.3651 10.3594 9 12.0002 9C12.5564 9 13.0724 9.15686 13.5115 9.42792L12.3842 10.5552C12.2612 10.5192 12.1323 10.5 12.0002 10.5C11.231 10.5 10.5684 11.1497 10.5684 12C10.5684 12.116 10.5807 12.2282 10.6039 12.3355Z'
fill='currentColor'
/>
<path d='M17.5 5L5 17.5' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,16 +0,0 @@
<svg width='100%' height='100%' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M5.01097 13.6526C4.30282 12.6533 4.30282 11.3467 5.01097 10.3474C6.26959 8.57133 8.66728 6 12 6C15.3327 6 17.7304 8.57133 18.989 10.3474C19.6972 11.3467 19.6972 12.6533 18.989 13.6526C17.7304 15.4287 15.3327 18 12 18C8.66728 18 6.26959 15.4287 5.01097 13.6526Z'
stroke='currentColor'
stroke-width='1.5'
stroke-linecap='round'
stroke-linejoin='round'
/>
<path
d='M11.9999 14.25C13.2049 14.25 14.1818 13.2426 14.1818 12C14.1818 10.7574 13.2049 9.75 11.9999 9.75C10.7949 9.75 9.81812 10.7574 9.81812 12C9.81812 13.2426 10.7949 14.25 11.9999 14.25Z'
stroke='currentColor'
stroke-width='1.5'
stroke-linecap='round'
stroke-linejoin='round'
/>
</svg>

Before

Width:  |  Height:  |  Size: 888 B

View File

@ -1,6 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.25 1.75H2.625C2.14175 1.75 1.75 2.14175 1.75 2.625V5.25C1.75 5.73325 2.14175 6.125 2.625 6.125H5.25C5.73325 6.125 6.125 5.73325 6.125 5.25V2.625C6.125 2.14175 5.73325 1.75 5.25 1.75Z" stroke="#333333" stroke-width="0.875" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.375 1.75H8.75C8.26675 1.75 7.875 2.14175 7.875 2.625V5.25C7.875 5.73325 8.26675 6.125 8.75 6.125H11.375C11.8582 6.125 12.25 5.73325 12.25 5.25V2.625C12.25 2.14175 11.8582 1.75 11.375 1.75Z" stroke="#333333" stroke-width="0.875" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.375 7.875H8.75C8.26675 7.875 7.875 8.26675 7.875 8.75V11.375C7.875 11.8582 8.26675 12.25 8.75 12.25H11.375C11.8582 12.25 12.25 11.8582 12.25 11.375V8.75C12.25 8.26675 11.8582 7.875 11.375 7.875Z" stroke="#333333" stroke-width="0.875" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.25 7.875H2.625C2.14175 7.875 1.75 8.26675 1.75 8.75V11.375C1.75 11.8582 2.14175 12.25 2.625 12.25H5.25C5.73325 12.25 6.125 11.8582 6.125 11.375V8.75C6.125 8.26675 5.73325 7.875 5.25 7.875Z" stroke="#333333" stroke-width="0.875" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.25368 3.6001H9.63368V12.0001H8.25368V8.3641H4.65368V12.0001H3.27368V3.6001H4.65368V7.0441H8.25368V3.6001Z" fill="#333333"/>
<path d="M12.0327 6.4001H12.9927V12.0001H11.8887V7.5681L10.8327 7.8641L10.5607 6.9201L12.0327 6.4001Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 359 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.67531 3.6001H9.05531V12.0001H7.67531V8.3641H4.07531V12.0001H2.69531V3.6001H4.07531V7.0441H7.67531V3.6001Z" fill="#333333"/>
<path d="M10.1104 12.0001V11.1761L12.0224 9.2081C12.449 8.7601 12.6624 8.38676 12.6624 8.0881C12.6624 7.86943 12.593 7.69343 12.4544 7.5601C12.321 7.42676 12.1477 7.3601 11.9344 7.3601C11.513 7.3601 11.201 7.57876 10.9984 8.0161L10.0704 7.4721C10.2464 7.0881 10.4997 6.79476 10.8304 6.5921C11.161 6.38943 11.5237 6.2881 11.9184 6.2881C12.425 6.2881 12.8597 6.4481 13.2224 6.7681C13.585 7.08276 13.7664 7.50943 13.7664 8.0481C13.7664 8.62943 13.4597 9.22677 12.8464 9.8401L11.7504 10.9361H13.8544V12.0001H10.1104Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 770 B

View File

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.62063 3.6001H9.00063V12.0001H7.62063V8.3641H4.02062V12.0001H2.64062V3.6001H4.02062V7.0441H7.62063V3.6001Z" fill="#333333"/>
<path d="M12.6637 8.6721C13.0424 8.7841 13.349 8.98143 13.5837 9.2641C13.8237 9.54143 13.9437 9.87743 13.9437 10.2721C13.9437 10.8481 13.749 11.2988 13.3597 11.6241C12.9757 11.9494 12.5037 12.1121 11.9437 12.1121C11.5064 12.1121 11.1144 12.0134 10.7677 11.8161C10.4264 11.6134 10.1784 11.3174 10.0237 10.9281L10.9677 10.3841C11.1064 10.8161 11.4317 11.0321 11.9437 11.0321C12.2264 11.0321 12.445 10.9654 12.5997 10.8321C12.7597 10.6934 12.8397 10.5068 12.8397 10.2721C12.8397 10.0428 12.7597 9.85876 12.5997 9.7201C12.445 9.58143 12.2264 9.5121 11.9437 9.5121H11.7037L11.2797 8.8721L12.3837 7.4321H10.1917V6.4001H13.7117V7.3121L12.6637 8.6721Z" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 901 B

View File

@ -1,6 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 5L3 8L6 11" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<rect width="4" height="1" rx="0.5" transform="matrix(-1 0 0 1 13 5)" fill="#333333"/>
<rect width="6" height="1" rx="0.5" transform="matrix(-1 0 0 1 13 7.5)" fill="#333333"/>
<rect width="4" height="1" rx="0.5" transform="matrix(-1 0 0 1 13 10)" fill="#333333"/>
</svg>

Before

Width:  |  Height:  |  Size: 457 B

Some files were not shown because too many files have changed in this diff Show More