UBERF-4946 Drive application (#5616)

This commit is contained in:
Alexander Onnikov
2024-05-18 19:46:52 +07:00
committed by GitHub
parent d91c3e5a44
commit 9337583bd8
95 changed files with 3698 additions and 53 deletions
+228 -4
View File
@@ -146,6 +146,15 @@ dependencies:
'@rush-temp/document-resources':
specifier: file:./projects/document-resources.tgz
version: file:projects/document-resources.tgz(@tiptap/pm@2.2.4)(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/drive':
specifier: file:./projects/drive.tgz
version: file:projects/drive.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/drive-assets':
specifier: file:./projects/drive-assets.tgz
version: file:projects/drive-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/drive-resources':
specifier: file:./projects/drive-resources.tgz
version: file:projects/drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/elastic':
specifier: file:./projects/elastic.tgz
version: file:projects/elastic.tgz(@types/node@20.11.19)(esbuild@0.20.1)
@@ -254,6 +263,9 @@ dependencies:
'@rush-temp/model-document':
specifier: file:./projects/model-document.tgz
version: file:projects/model-document.tgz
'@rush-temp/model-drive':
specifier: file:./projects/model-drive.tgz
version: file:projects/model-drive.tgz
'@rush-temp/model-gmail':
specifier: file:./projects/model-gmail.tgz
version: file:projects/model-gmail.tgz
@@ -308,6 +320,9 @@ dependencies:
'@rush-temp/model-server-document':
specifier: file:./projects/model-server-document.tgz
version: file:projects/model-server-document.tgz
'@rush-temp/model-server-drive':
specifier: file:./projects/model-server-drive.tgz
version: file:projects/model-server-drive.tgz
'@rush-temp/model-server-gmail':
specifier: file:./projects/model-server-gmail.tgz
version: file:projects/model-server-gmail.tgz
@@ -530,6 +545,12 @@ dependencies:
'@rush-temp/server-document-resources':
specifier: file:./projects/server-document-resources.tgz
version: file:projects/server-document-resources.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/server-drive':
specifier: file:./projects/server-drive.tgz
version: file:projects/server-drive.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/server-drive-resources':
specifier: file:./projects/server-drive-resources.tgz
version: file:projects/server-drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/server-gmail':
specifier: file:./projects/server-gmail.tgz
version: file:projects/server-gmail.tgz(esbuild@0.20.1)(ts-node@10.9.2)
@@ -19610,6 +19631,113 @@ packages:
- ts-node
dev: false
file:projects/drive-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-nSFnPSUpwp1cjsI5P3PnJmba3E3A9Cz4c2ZuiL51QjbtuAY7ctFZsveQrV6sfbfS/c+vvoBfw+WXcBXcPrlnXA==, tarball: file:projects/drive-assets.tgz}
id: file:projects/drive-assets.tgz
name: '@rush-temp/drive-assets'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@types/node': 20.11.19
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-E2zO+OiX83Ig7B8ZJHiKJZydZf0RqPxXrHBnGl99l3biAcxNId4UJryMUM8KC7PQQw16ue/bERRU1dOBIs9Hng==, tarball: file:projects/drive-resources.tgz}
id: file:projects/drive-resources.tgz
name: '@rush-temp/drive-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.12)(ts-node@10.9.2)
fast-equals: 5.0.1
filesize: 8.0.7
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.12)
sass: 1.71.1
svelte: 4.2.12
svelte-check: 3.6.9(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)
svelte-eslint-parser: 0.33.1(svelte@4.2.12)
svelte-loader: 3.2.0(svelte@4.2.12)
svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)(typescript@5.3.3)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- coffeescript
- esbuild
- less
- node-notifier
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
- ts-node
dev: false
file:projects/drive.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-Av+Kk9dyNTpd9XtrMgx5Sf9+GUNDMcLDpVT+dlCdJqgcmweVQXGC81AGXDapuzoruVN0x5JdAwGL127Z3kOfxA==, tarball: file:projects/drive.tgz}
id: file:projects/drive.tgz
name: '@rush-temp/drive'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/elastic.tgz(@types/node@20.11.19)(esbuild@0.20.1):
resolution: {integrity: sha512-iUKt8+1LhjnXTCYZv0DssFNwm1pHB+ioIePMutpYZrtO7HvwaZN4kHVneHjk5hA0EeE96JOkCwgYcPka35UvuA==, tarball: file:projects/elastic.tgz}
id: file:projects/elastic.tgz
@@ -20553,7 +20681,7 @@ packages:
dev: false
file:projects/model-all.tgz:
resolution: {integrity: sha512-2OrA8LVIJi2SmLHH/eIPGz3aWSD38swpk1SGyko75AbEoyJAXU6NHR8eFLbCGJIEOhF889Z9aJmXtAkuI7zpbw==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-eT/WXN3UKeAlTfImDOcLVfmHGhZo/yinjhkqvpVSSCGWJhiCJBNX5vJLn9HkEem6Xh9/YCMxp2MN49n50bV7+Q==, tarball: file:projects/model-all.tgz}
name: '@rush-temp/model-all'
version: 0.0.0
dependencies:
@@ -20729,6 +20857,24 @@ packages:
- supports-color
dev: false
file:projects/model-drive.tgz:
resolution: {integrity: sha512-6asmZyk0a+N8c+cmsmRDk1pq0IuMGpo3cDJefSYGyXdljeEbPyQVgidO2vbUXVLJ8NYZiZZDzWrWCww7t+tHaQ==, tarball: file:projects/model-drive.tgz}
name: '@rush-temp/model-drive'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
prettier: 3.2.5
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: false
file:projects/model-gmail.tgz:
resolution: {integrity: sha512-Bmsl1/2h/DX6G5njEjb0hpKefll9pkcWV+4DyMW3KrQyz1lcFswWRSCK9fg6u3XrGe3QkMZrmikmdqR06WOV+Q==, tarball: file:projects/model-gmail.tgz}
name: '@rush-temp/model-gmail'
@@ -21073,6 +21219,24 @@ packages:
- supports-color
dev: false
file:projects/model-server-drive.tgz:
resolution: {integrity: sha512-8VZ/UCnjAiS9ZDi6oSVh1G+u6dXtO+xrWknFs2J8KUTiNcfqE98s3ok84QiR6LsMtS0Jn+JYgLppYSqL73P3/A==, tarball: file:projects/model-server-drive.tgz}
name: '@rush-temp/model-server-drive'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
prettier: 3.2.5
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: false
file:projects/model-server-gmail.tgz:
resolution: {integrity: sha512-wBrT0XWhQWs1JvlfOedP/YfmKxLdG5Ik3sv94faSTkM0JCbDielAHEzcYqDZ/W5D1nblBrDnr/rtwJ+BZ6P+UQ==, tarball: file:projects/model-server-gmail.tgz}
name: '@rush-temp/model-server-gmail'
@@ -22107,7 +22271,7 @@ packages:
dev: false
file:projects/pod-server.tgz:
resolution: {integrity: sha512-XHR0YptOBL1XIE0gR5RfXn8BQ+LXdWdB0t8C5S3czVnBjQccPTlsYBN1IrG9jWzUFGxPq9vGUy9Kd+dgr2KvbQ==, tarball: file:projects/pod-server.tgz}
resolution: {integrity: sha512-On9iUbvTRMtKD+Eqe4/LgKjk4y8MwXP/VaqQ+5nnmOra1nXnMQHwkaNnT7eYhWLoH4rM88JN9RyifT8UmF7dQQ==, tarball: file:projects/pod-server.tgz}
name: '@rush-temp/pod-server'
version: 0.0.0
dependencies:
@@ -22252,7 +22416,7 @@ packages:
dev: false
file:projects/prod.tgz(bufferutil@4.0.8)(sass@1.71.1)(ts-node@10.9.2)(utf-8-validate@6.0.3):
resolution: {integrity: sha512-ApNm3LLGsu8aU/ySQbpHVWR+td2eUUeCzgjnkwdw83E4QZ9mFj8I9hTN0z4gC32PAOFdxRt6P0J8vA78WBNfGw==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-PgH0PbsoN0q+n48ghZJrY6pRprrEUTaGK7z1o+5XMmxRKgbT3OCFTn9xhEJxIylL4o96vr1r5WLiQupebAU//w==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@@ -23189,6 +23353,66 @@ packages:
- ts-node
dev: false
file:projects/server-drive-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-KIf7tcrSge1i02B2Mfr7Nczi85c8mloCuuydcoui/uBREzyQDMvp5alcagh7qxxi+tdcuyxRxu6VBnRnM5Nm4w==, tarball: file:projects/server-drive-resources.tgz}
id: file:projects/server-drive-resources.tgz
name: '@rush-temp/server-drive-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/server-drive.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-PmYaAL/QMvNdU7M+vPzdVNOS5oZcpRUKS/qFFHEFN3U1RyROsFxmJ0802+KC1t+H6yRRBsZbB8bBtyj7u/mPLw==, tarball: file:projects/server-drive.tgz}
id: file:projects/server-drive.tgz
name: '@rush-temp/server-drive'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@types/node': 20.11.19
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/server-gmail-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-70qdzXaq0DidVubkXv7DmuuLqdBy+rWVOLnJASmTKKYKcmZualQI2SxmDM2wGwuGGmHNup0stFpMswL+pV5u+A==, tarball: file:projects/server-gmail-resources.tgz}
id: file:projects/server-gmail-resources.tgz
@@ -25432,7 +25656,7 @@ packages:
dev: false
file:projects/tool.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.3):
resolution: {integrity: sha512-Hvo30pcgIBmHUOHsuLDFZLI77rv4hq/MsyZ3xAcIJFFuQc/reUgbW3LX5wlRI0lsbMhyxf9uw5jWjWoDPZKa8A==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-iVwH4zmc67aEXW4wiOyvZpR05m8vmI11z7r/l/Ze+0inUoteke9f3iP4EeXQqPjw/tSlJnFUUPmW7/PUyTY1Xg==, tarball: file:projects/tool.tgz}
id: file:projects/tool.tgz
name: '@rush-temp/tool'
version: 0.0.0
+5
View File
@@ -167,6 +167,11 @@
"@hcengineering/bitrix": "^0.6.44",
"@hcengineering/bitrix-assets": "^0.6.0",
"@hcengineering/bitrix-resources": "^0.6.0",
"@hcengineering/drive": "^0.6.0",
"@hcengineering/drive-assets": "^0.6.0",
"@hcengineering/drive-resources": "^0.6.0",
"@hcengineering/server-drive": "^0.6.0",
"@hcengineering/server-drive-resources": "^0.6.0",
"@hcengineering/support": "^0.6.1",
"@hcengineering/support-assets": "^0.6.1",
"@hcengineering/support-resources": "^0.6.0",
+4
View File
@@ -23,6 +23,7 @@ import { chunterId } from '@hcengineering/chunter'
import client, { clientId } from '@hcengineering/client'
import contactPlugin, { contactId } from '@hcengineering/contact'
import { documentId } from '@hcengineering/document'
import { driveId } from '@hcengineering/drive'
import gmail, { gmailId } from '@hcengineering/gmail'
import guest, { guestId } from '@hcengineering/guest'
import { hrId } from '@hcengineering/hr'
@@ -56,6 +57,7 @@ import '@hcengineering/calendar-assets'
import '@hcengineering/chunter-assets'
import '@hcengineering/contact-assets'
import '@hcengineering/document-assets'
import '@hcengineering/drive-assets'
import '@hcengineering/gmail-assets'
import '@hcengineering/guest-assets'
import '@hcengineering/hr-assets'
@@ -121,6 +123,7 @@ function configureI18n(): void {
addStringsLoader(calendarId, async (lang: string) => await import(`@hcengineering/calendar-assets/lang/${lang}.json`))
addStringsLoader(chunterId, async (lang: string) => await import(`@hcengineering/chunter-assets/lang/${lang}.json`))
addStringsLoader(contactId, async (lang: string) => await import(`@hcengineering/contact-assets/lang/${lang}.json`))
addStringsLoader(driveId, async (lang: string) => await import(`@hcengineering/drive-assets/lang/${lang}.json`))
addStringsLoader(gmailId, async (lang: string) => await import(`@hcengineering/gmail-assets/lang/${lang}.json`))
addStringsLoader(hrId, async (lang: string) => await import(`@hcengineering/hr-assets/lang/${lang}.json`))
addStringsLoader(inventoryId, async (lang: string) => await import(`@hcengineering/inventory-assets/lang/${lang}.json`))
@@ -232,6 +235,7 @@ export async function configurePlatform() {
addLocation(documentId, () => import(/* webpackChunkName: "document" */ '@hcengineering/document-resources'))
addLocation(guestId, () => import(/* webpackChunkName: "guest" */ '@hcengineering/guest-resources'))
addLocation(timeId, () => import(/* webpackChunkName: "time" */ '@hcengineering/time-resources'))
addLocation(driveId, () => import(/* webpackChunkName: "drive" */ '@hcengineering/drive-resources'))
setMetadata(client.metadata.FilterModel, true)
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+2
View File
@@ -95,6 +95,8 @@
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/server-document": "^0.6.0",
"@hcengineering/server-document-resources": "^0.6.0",
"@hcengineering/server-drive": "^0.6.0",
"@hcengineering/server-drive-resources": "^0.6.0",
"@hcengineering/server-gmail": "^0.6.0",
"@hcengineering/server-gmail-resources": "^0.6.0",
"@hcengineering/server-hr": "^0.6.0",
+2
View File
@@ -27,6 +27,7 @@ import { serverCalendarId } from '@hcengineering/server-calendar'
import { serverChunterId } from '@hcengineering/server-chunter'
import { serverCollaborationId } from '@hcengineering/server-collaboration'
import { serverContactId } from '@hcengineering/server-contact'
import { serverDriveId } from '@hcengineering/server-drive'
import { serverDocumentId } from '@hcengineering/server-document'
import { serverGmailId } from '@hcengineering/server-gmail'
import { serverGuestId } from '@hcengineering/server-guest'
@@ -66,6 +67,7 @@ addLocation(serverViewId, () => import('@hcengineering/server-view-resources'))
addLocation(serverDocumentId, () => import('@hcengineering/server-document-resources'))
addLocation(serverTimeId, () => import('@hcengineering/server-time-resources'))
addLocation(serverGuestId, () => import('@hcengineering/server-guest-resources'))
addLocation(serverDriveId, () => import('@hcengineering/server-drive-resources'))
function prepareTools (): {
mongodbUri: string
+3 -1
View File
@@ -88,6 +88,8 @@
"@hcengineering/model-document": "^0.6.0",
"@hcengineering/model-server-document": "^0.6.0",
"@hcengineering/model-time": "^0.6.0",
"@hcengineering/model-server-time": "^0.6.0"
"@hcengineering/model-server-time": "^0.6.0",
"@hcengineering/model-drive": "^0.6.0",
"@hcengineering/model-server-drive": "^0.6.0"
}
}
+5 -1
View File
@@ -46,6 +46,7 @@ import {
import { serverContactId, createModel as serverContactModel } from '@hcengineering/model-server-contact'
import { serverCoreId, createModel as serverCoreModel } from '@hcengineering/model-server-core'
import { serverDocumentId, createModel as serverDocumentModel } from '@hcengineering/model-server-document'
import { serverDriveId, createModel as serverDriveModel } from '@hcengineering/model-server-drive'
import { serverGmailId, createModel as serverGmailModel } from '@hcengineering/model-server-gmail'
import { serverGuestId, createModel as serverGuestModel } from '@hcengineering/model-server-guest'
import { serverHrId, createModel as serverHrModel } from '@hcengineering/model-server-hr'
@@ -63,6 +64,7 @@ import { serverTimeId, createModel as serverTimeModel } from '@hcengineering/mod
import { serverTrackerId, createModel as serverTrackerModel } from '@hcengineering/model-server-tracker'
import { serverViewId, createModel as serverViewModel } from '@hcengineering/model-server-view'
import setting, { settingId, createModel as settingModel } from '@hcengineering/model-setting'
import { driveId, createModel as driveModel } from '@hcengineering/model-drive'
import { supportId, createModel as supportModel } from '@hcengineering/model-support'
import { tagsId, createModel as tagsModel } from '@hcengineering/model-tags'
import { taskId, createModel as taskModel } from '@hcengineering/model-task'
@@ -296,6 +298,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
}
],
[timeModel, timeId],
[driveModel, driveId],
[supportModel, supportId],
[serverCoreModel, serverCoreId],
@@ -323,7 +326,8 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
[serverOpenAI, openAIId],
[serverDocumentModel, serverDocumentId],
[serverTimeModel, serverTimeId],
[serverGuestModel, serverGuestId]
[serverGuestModel, serverGuestId],
[serverDriveModel, serverDriveId]
]
for (const [b, id, config] of builders) {
-1
View File
@@ -21,7 +21,6 @@ export default mergeIds(coreId, core, {
Archived: '' as IntlString,
ClassLabel: '' as IntlString,
ClassPropertyLabel: '' as IntlString,
Members: '' as IntlString,
Blob: '' as IntlString,
BlobContentType: '' as IntlString,
BlobEtag: '' as IntlString,
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@hcengineering/model-drive",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"template": "@hcengineering/model-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/model": "^0.6.7",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-preference": "^0.6.0",
"@hcengineering/model-tracker": "^0.6.0",
"@hcengineering/model-view": "^0.6.0",
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/drive": "^0.6.0",
"@hcengineering/drive-resources": "^0.6.0",
"@hcengineering/ui": "^0.6.11",
"@hcengineering/view": "^0.6.9"
}
}
+423
View File
@@ -0,0 +1,423 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, {
type Domain,
type Role,
type RolesAssignment,
type Type,
Account,
AccountRole,
Ref,
IndexKind,
DOMAIN_MODEL,
type Blob
} from '@hcengineering/core'
import { type Drive, type File, type Folder, type Resource, driveId } from '@hcengineering/drive'
import { type Builder, Model, UX, Mixin, Prop, TypeString, Index, TypeRef, ReadOnly } from '@hcengineering/model'
import { TDoc, TType, TTypedSpace } from '@hcengineering/model-core'
import tracker from '@hcengineering/model-tracker'
import view, { type Viewlet, classPresenter, createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import { getEmbeddedLabel } from '@hcengineering/platform'
import drive from './plugin'
export { driveId } from '@hcengineering/drive'
export { drive as default }
export const DOMAIN_DRIVE = 'drive' as Domain
/** @public */
export function TypeFilesize (): Type<number> {
return { _class: drive.class.TypeFileSize, label: drive.string.Size }
}
@Model(drive.class.TypeFileSize, core.class.Type, DOMAIN_MODEL)
export class TTypeFileSize extends TType {}
@Model(drive.class.Drive, core.class.TypedSpace)
@UX(drive.string.Drive)
export class TDrive extends TTypedSpace implements Drive {}
@Mixin(drive.mixin.DefaultDriveTypeData, drive.class.Drive)
@UX(getEmbeddedLabel('Default drive type'))
export class TDefaultDriveTypeData extends TDrive implements RolesAssignment {
[key: Ref<Role>]: Ref<Account>[]
}
@Model(drive.class.Resource, core.class.Doc, DOMAIN_DRIVE)
@UX(drive.string.Resource)
export class TResource extends TDoc implements Resource {
declare space: Ref<Drive>
@Prop(TypeString(), drive.string.Name)
@Index(IndexKind.FullText)
name!: string
@Prop(TypeRef(core.class.Blob), drive.string.File)
@ReadOnly()
file?: Ref<Blob>
@Prop(TypeRef(drive.class.Resource), drive.string.Parent)
@Index(IndexKind.Indexed)
@ReadOnly()
parent!: Ref<Resource>
@Prop(TypeRef(drive.class.Resource), drive.string.Path)
@ReadOnly()
path!: Ref<Resource>[]
}
@Model(drive.class.Folder, drive.class.Resource, DOMAIN_DRIVE)
@UX(drive.string.Folder)
export class TFolder extends TResource implements Folder {
@Prop(TypeRef(drive.class.Folder), drive.string.Parent)
@Index(IndexKind.Indexed)
@ReadOnly()
declare parent: Ref<Folder>
@Prop(TypeRef(drive.class.Folder), drive.string.Path)
@ReadOnly()
declare path: Ref<Folder>[]
declare file: undefined
}
@Model(drive.class.File, drive.class.Resource, DOMAIN_DRIVE)
@UX(drive.string.File)
export class TFile extends TResource implements File {
@Prop(TypeRef(core.class.Blob), drive.string.File)
@ReadOnly()
declare file: Ref<Blob>
@Prop(TypeRef(drive.class.Folder), drive.string.Parent)
@Index(IndexKind.Indexed)
@ReadOnly()
declare parent: Ref<Folder>
@Prop(TypeRef(drive.class.Folder), drive.string.Path)
@ReadOnly()
declare path: Ref<Folder>[]
}
function defineDrive (builder: Builder): void {
builder.createModel(TDrive, TDefaultDriveTypeData)
builder.mixin(drive.class.Drive, core.class.Class, view.mixin.ObjectPresenter, {
presenter: drive.component.DrivePresenter
})
builder.mixin(drive.class.Drive, core.class.Class, view.mixin.ObjectPanel, {
component: drive.component.DrivePanel
})
builder.mixin(drive.class.Drive, core.class.Class, view.mixin.LinkProvider, {
encode: drive.function.DriveLinkProvider
})
// Space type
builder.createDoc(
core.class.SpaceTypeDescriptor,
core.space.Model,
{
name: drive.string.Drive,
description: drive.string.Description,
icon: drive.icon.Drive,
baseClass: drive.class.Drive,
availablePermissions: [
core.permission.UpdateSpace,
core.permission.ArchiveSpace,
core.permission.ForbidDeleteObject
]
},
drive.descriptor.DriveType
)
builder.createDoc(
core.class.SpaceType,
core.space.Model,
{
name: 'Default drive type',
descriptor: drive.descriptor.DriveType,
roles: 0,
targetClass: drive.mixin.DefaultDriveTypeData
},
drive.spaceType.DefaultDrive
)
// Navigator
builder.mixin(drive.class.Drive, core.class.Class, view.mixin.SpacePresenter, {
presenter: drive.component.DriveSpacePresenter
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: drive.class.Drive,
descriptor: view.viewlet.Table,
configOptions: {
hiddenKeys: ['name', 'description']
},
config: ['', 'members', 'owners', 'private', 'archived']
},
drive.viewlet.DriveTable
)
// Actions
builder.mixin(drive.class.Drive, core.class.Class, view.mixin.IgnoreActions, {
actions: [tracker.action.EditRelatedTargets, tracker.action.NewRelatedIssue]
})
createAction(
builder,
{
action: drive.actionImpl.EditDrive,
label: drive.string.EditDrive,
icon: view.icon.Edit,
input: 'focus',
category: drive.category.Drive,
target: drive.class.Drive,
visibilityTester: view.function.CanEditSpace,
query: {},
context: {
mode: ['context', 'browser'],
group: 'edit'
}
},
drive.action.EditDrive
)
createAction(
builder,
{
action: drive.actionImpl.CreateRootFolder,
label: drive.string.CreateFolder,
icon: drive.icon.Folder,
category: drive.category.Drive,
input: 'none',
target: drive.class.Drive,
context: {
mode: ['context', 'browser'],
application: drive.app.Drive,
group: 'create'
}
},
drive.action.CreateRootFolder
)
}
function defineResource (builder: Builder): void {
builder.createModel(TTypeFileSize, TResource)
classPresenter(builder, drive.class.TypeFileSize, drive.component.FileSizePresenter)
builder.mixin(drive.class.Resource, core.class.Class, view.mixin.ObjectPresenter, {
presenter: drive.component.ResourcePresenter
})
builder.createDoc<Viewlet>(
view.class.Viewlet,
core.space.Model,
{
attachTo: drive.class.Resource,
descriptor: view.viewlet.Table,
config: [
{
key: '',
presenter: drive.component.ResourcePresenter,
label: drive.string.Name
},
{
key: '$lookup.file.size',
presenter: drive.component.FileSizePresenter,
label: drive.string.Size
},
{
key: '$lookup.file.modifiedOn'
},
'createdBy'
],
configOptions: {
hiddenKeys: ['name', 'file', 'parent', 'path'],
sortable: true
}
},
drive.viewlet.FileTable
)
// builder.createDoc<Viewlet>(
// view.class.Viewlet,
// core.space.Model,
// {
// attachTo: drive.class.Resource,
// descriptor: drive.viewlet.Grid,
// config: ['', 'type', 'size', 'lastModified', 'createdBy'],
// configOptions: {
// hiddenKeys: ['name', 'file', 'parent', 'path'],
// sortable: true
// }
// },
// drive.viewlet.FileGrid
// )
}
function defineFolder (builder: Builder): void {
builder.createModel(TFolder)
builder.mixin(drive.class.Folder, core.class.Class, view.mixin.ObjectPresenter, {
presenter: drive.component.FolderPresenter
})
builder.mixin(drive.class.Folder, core.class.Class, view.mixin.ObjectEditor, {
editor: drive.component.EditFolder
})
builder.mixin(drive.class.Folder, core.class.Class, view.mixin.ObjectPanel, {
component: drive.component.FolderPanel
})
builder.mixin(drive.class.Folder, core.class.Class, view.mixin.LinkProvider, {
encode: drive.function.FolderLinkProvider
})
// Actions
builder.mixin(drive.class.Folder, core.class.Class, view.mixin.IgnoreActions, {
actions: [
view.action.Open,
view.action.OpenInNewTab,
tracker.action.EditRelatedTargets,
tracker.action.NewRelatedIssue
]
})
createAction(
builder,
{
action: drive.actionImpl.CreateChildFolder,
label: drive.string.CreateFolder,
icon: drive.icon.Folder,
category: drive.category.Drive,
input: 'none',
target: drive.class.Folder,
context: {
mode: ['context', 'browser'],
application: drive.app.Drive,
group: 'create'
}
},
drive.action.CreateChildFolder
)
}
function defineFile (builder: Builder): void {
builder.createModel(TFile)
builder.mixin(drive.class.File, core.class.Class, view.mixin.ObjectPresenter, {
presenter: drive.component.FilePresenter
})
// Actions
builder.mixin(drive.class.File, core.class.Class, view.mixin.IgnoreActions, {
actions: [view.action.OpenInNewTab, tracker.action.EditRelatedTargets, tracker.action.NewRelatedIssue]
})
createAction(
builder,
{
action: drive.actionImpl.DownloadFile,
label: drive.string.Download,
icon: drive.icon.Download,
category: drive.category.Drive,
input: 'none',
target: drive.class.File,
context: {
mode: ['context', 'browser'],
application: drive.app.Drive,
group: 'tools'
}
},
drive.action.DownloadFile
)
}
function defineApplication (builder: Builder): void {
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: drive.string.Drive,
icon: drive.icon.Drive,
alias: driveId,
hidden: false,
locationResolver: drive.resolver.Location,
navigatorModel: {
specials: [
{
id: 'browser',
accessLevel: AccountRole.User,
label: drive.string.Drives,
icon: view.icon.List,
component: workbench.component.SpecialView,
componentProps: {
_class: drive.class.Drive,
label: drive.string.Drives
},
position: 'top'
}
],
spaces: [
{
id: 'drives',
label: drive.string.Drive,
spaceClass: drive.class.Drive,
addSpaceLabel: drive.string.CreateDrive,
createComponent: drive.component.CreateDrive,
icon: drive.icon.Drive,
// intentionally left empty in order to make space presenter working
specials: []
}
]
},
navHeaderComponent: drive.component.DriveSpaceHeader
},
drive.app.Drive
)
}
export function createModel (builder: Builder): void {
builder.createDoc(
view.class.ViewletDescriptor,
core.space.Model,
{
label: view.string.Grid,
icon: view.icon.Table,
component: drive.component.GridView
},
drive.viewlet.Grid
)
defineDrive(builder)
defineResource(builder)
defineFolder(builder)
defineFile(builder)
defineApplication(builder)
}
+81
View File
@@ -0,0 +1,81 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Doc, Ref } from '@hcengineering/core'
import {} from '@hcengineering/core'
import { driveId } from '@hcengineering/drive'
import drive from '@hcengineering/drive-resources/src/plugin'
import { type IntlString, type Resource, mergeIds } from '@hcengineering/platform'
import { type AnyComponent, type Location } from '@hcengineering/ui'
import {
type Action,
type ActionCategory,
type ViewAction,
type Viewlet,
type ViewletDescriptor
} from '@hcengineering/view'
export default mergeIds(driveId, drive, {
component: {
GridView: '' as AnyComponent,
CreateDrive: '' as AnyComponent,
DriveSpaceHeader: '' as AnyComponent,
DriveSpacePresenter: '' as AnyComponent,
DrivePanel: '' as AnyComponent,
DrivePresenter: '' as AnyComponent,
EditFolder: '' as AnyComponent,
FolderPanel: '' as AnyComponent,
FolderPresenter: '' as AnyComponent,
FilePresenter: '' as AnyComponent,
FileSizePresenter: '' as AnyComponent,
ResourcePresenter: '' as AnyComponent
},
function: {
DriveLinkProvider: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>,
FolderLinkProvider: '' as Resource<(doc: Doc, props: Record<string, any>) => Promise<Location>>
},
viewlet: {
Grid: '' as Ref<ViewletDescriptor>,
DriveTable: '' as Ref<Viewlet>,
FileTable: '' as Ref<Viewlet>,
FileGrid: '' as Ref<Viewlet>
},
category: {
Drive: '' as Ref<ActionCategory>
},
action: {
CreateChildFolder: '' as Ref<Action>,
CreateRootFolder: '' as Ref<Action>,
EditDrive: '' as Ref<Action>,
DownloadFile: '' as Ref<Action>
},
actionImpl: {
CreateChildFolder: '' as ViewAction,
CreateRootFolder: '' as ViewAction,
EditDrive: '' as ViewAction,
DownloadFile: '' as ViewAction
},
string: {
Name: '' as IntlString,
Description: '' as IntlString,
Size: '' as IntlString,
Type: '' as IntlString,
LastModified: '' as IntlString,
Parent: '' as IntlString,
Path: '' as IntlString,
Drives: '' as IntlString,
Download: '' as IntlString
}
})
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@hcengineering/model-server-drive",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"template": "@hcengineering/model-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/model": "^0.6.7",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/drive": "^0.6.0",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/server-drive": "^0.6.0"
}
}
+42
View File
@@ -0,0 +1,42 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Builder } from '@hcengineering/model'
import core, { type Class, type Doc } from '@hcengineering/core'
import drive from '@hcengineering/drive'
import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core'
import serverDrive from '@hcengineering/server-drive'
export { serverDriveId } from '@hcengineering/server-drive'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverDrive.trigger.OnFileDelete,
txMatch: {
_class: core.class.TxRemoveDoc,
objectClass: drive.class.File
}
})
builder.mixin<Class<Doc>, ObjectDDParticipant>(
drive.class.Folder,
core.class.Class,
serverCore.mixin.ObjectDDParticipant,
{
collectDocs: serverDrive.function.FindFolderResources
}
)
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+1
View File
@@ -217,6 +217,7 @@ export default plugin(coreId, {
Account: '' as IntlString,
StatusCategory: '' as IntlString,
Rank: '' as IntlString,
Members: '' as IntlString,
Owners: '' as IntlString,
Permission: '' as IntlString,
CreateObject: '' as IntlString,
@@ -70,7 +70,7 @@
$: isNew = teamspace === undefined
let typeId: Ref<SpaceType> | undefined = teamspace?.type || document.spaceType.DefaultTeamspaceType
let typeId: Ref<SpaceType> | undefined = teamspace?.type ?? document.spaceType.DefaultTeamspaceType
let spaceType: WithLookup<SpaceType> | undefined
$: void loadSpaceType(typeId)
+1 -4
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import type { Class, Doc, Mixin, Ref, Space, SpaceType, SpaceTypeDescriptor } from '@hcengineering/core'
import type { Class, Doc, Mixin, Ref, SpaceType, SpaceTypeDescriptor } from '@hcengineering/core'
import { NotificationGroup, NotificationType } from '@hcengineering/notification'
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
import { IntlString, plugin } from '@hcengineering/platform'
@@ -60,9 +60,6 @@ export const documentPlugin = plugin(documentId, {
Star: '' as Asset,
Starred: '' as Asset
},
space: {
Documents: '' as Ref<Space>
},
app: {
Documents: '' as Ref<Doc>
},
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="drive" viewBox="0 0 32 32">
<path d="M16 3C10.7021 3 5 4.252 5 7V25C5 27.748 10.7021 29 16 29C21.2979 29 27 27.748 27 25V7C27 4.252 21.2979 3 16 3ZM16 5C21.7976 5 24.7949 6.4341 24.9968 7C24.7949 7.5659 21.7976 9 16 9C10.1587 9 7.1606 7.5444 7 7.0176V7.0127C7.1606 6.4556 10.1587 5 16 5ZM7 9.4277C9.1279 10.4951 12.6426 11 16 11C19.3574 11 22.8721 10.4951 25 9.4277V12.9873C24.8394 13.5444 21.8413 15 16 15C10.1499 15 7.1509 13.54 7 13V9.4277ZM7 15.4277C9.1279 16.4951 12.6426 17 16 17C19.3574 17 22.8721 16.4951 25 15.4277V18.9873C24.8394 19.5444 21.8413 21 16 21C10.1499 21 7.1509 19.54 7 19V15.4277ZM16 27C10.1499 27 7.1509 25.54 7 25V21.4277C9.1279 22.4951 12.6426 23 16 23C19.3574 23 22.8721 22.4951 25 21.4277V24.9873C24.8394 25.5444 21.8413 27 16 27Z" fill="currentColor"/>
</symbol>
<symbol id="file" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6Z" fill="currentColor"/>
</symbol>
<symbol id="folder" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 9C2 6.23858 4.23858 4 7 4H10.6716C11.4672 4 12.2303 4.31607 12.7929 4.87868L15.6213 7.70711C15.8089 7.89464 16.0632 8 16.3284 8H25C27.7614 8 30 10.2386 30 13V23C30 25.7614 27.7614 28 25 28H7C4.23858 28 2 25.7614 2 23V9ZM7 6C5.34315 6 4 7.34315 4 9V23C4 24.6569 5.34315 26 7 26H25C26.6569 26 28 24.6569 28 23V13C28 11.3431 26.6569 10 25 10H16.3284C15.5328 10 14.7697 9.68393 14.2071 9.12132L11.3787 6.29289C11.1911 6.10536 10.9368 6 10.6716 6H7Z" fill="currentColor"/>
</symbol>
<symbol id="folder-open" viewBox="0 0 33 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M28 14V13C28 11.3431 26.6569 10 25 10H16.3284C15.5328 10 14.7697 9.68393 14.2071 9.12132L11.3787 6.29289C11.1911 6.10536 10.9368 6 10.6716 6H7C5.34315 6 4 7.34315 4 9V21L5.17111 16.9011C5.66174 15.1839 7.23128 14 9.01721 14H28Z" fill="currentColor" fill-opacity="0.1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 4C4.23858 4 2 6.23858 2 9V23C2 25.7614 4.23858 28 7 28H26.9828C28.7687 28 30.3383 26.8161 30.8289 25.0989L32.5432 19.0989C33.147 16.9856 31.9391 14.8804 30 14.2152V13C30 10.2386 27.7614 8 25 8H16.3284C16.0632 8 15.8089 7.89464 15.6213 7.70711L12.7929 4.87868C12.2303 4.31607 11.4672 4 10.6716 4H7ZM28 14V13C28 11.3431 26.6569 10 25 10H16.3284C15.5328 10 14.7697 9.68393 14.2071 9.12132L11.3787 6.29289C11.1911 6.10536 10.9368 6 10.6716 6H7C5.34315 6 4 7.34315 4 9V21L5.17111 16.9011C5.66174 15.1839 7.23128 14 9.01721 14H28Z" fill="currentColor"/>
</symbol>
<symbol id="folder-closed" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 9C2 6.23858 4.23858 4 7 4H10.6716C11.4672 4 12.2303 4.31607 12.7929 4.87868L15.6213 7.70711C15.8089 7.89464 16.0632 8 16.3284 8H25C27.7614 8 30 10.2386 30 13V23C30 25.7614 27.7614 28 25 28H7C4.23858 28 2 25.7614 2 23V9ZM7 6C5.34315 6 4 7.34315 4 9V23C4 24.6569 5.34315 26 7 26H25C26.6569 26 28 24.6569 28 23V13C28 11.3431 26.6569 10 25 10H16.3284C15.5328 10 14.7697 9.68393 14.2071 9.12132L11.3787 6.29289C11.1911 6.10536 10.9368 6 10.6716 6H7Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 6C5.34315 6 4 7.34315 4 9V23C4 24.6569 5.34315 26 7 26H25C26.6569 26 28 24.6569 28 23V13C28 11.3431 26.6569 10 25 10H16.3284C15.5328 10 14.7697 9.68393 14.2071 9.12132L11.3787 6.29289C11.1911 6.10536 10.9368 6 10.6716 6H7Z" fill="currentColor" fill-opacity="0.1"/>
</symbol>
<symbol id="download" viewBox="0 0 24 24">
<path d="M18.7,9.6C18.5,6,15.6,3.2,12,3.2C8.4,3.2,5.5,6,5.3,9.5c-2.3,0.7-4,2.9-4,5.5c0,3.2,2.6,5.8,5.8,5.8 c0.5,0,1.1-0.1,1.6-0.2C9,20.4,9.2,20,9.1,19.6S8.6,19,8.2,19.1c-0.4,0.1-0.8,0.2-1.2,0.2c-2.3,0-4.2-1.9-4.2-4.2 c0-2,1.4-3.7,3.3-4.1c0.2,0,0.3-0.1,0.4-0.2c0.2-0.1,0.4-0.4,0.4-0.6c0-2.9,2.4-5.2,5.2-5.2s5.2,2.4,5.2,5.2c0,0.1,0,0.1,0,0.2 c0,0.3,0.2,0.6,0.6,0.7c1.9,0.4,3.3,2.1,3.3,4.1c0,2.3-1.9,4.2-4.2,4.2c-0.4,0-0.8-0.1-1.2-0.2c-0.4-0.1-0.8,0.1-0.9,0.5 c-0.1,0.4,0.1,0.8,0.5,0.9c0.5,0.1,1,0.2,1.6,0.2c3.2,0,5.8-2.6,5.8-5.8C22.6,12.5,21,10.4,18.7,9.6z" />
<path d="M16.1,14.5c-0.3-0.3-0.8-0.3-1.1,0l-2.3,2.3V10c0-0.4-0.3-0.8-0.8-0.8s-0.8,0.3-0.8,0.8v6.7l-2.2-2.2 c-0.3-0.3-0.8-0.3-1.1,0c-0.3,0.3-0.3,0.8,0,1.1l3.5,3.5c0.3,0.3,0.8,0.3,1.1,0l3.6-3.5C16.4,15.2,16.4,14.8,16.1,14.5z" />
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "assets"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+24
View File
@@ -0,0 +1,24 @@
{
"string": {
"Drive": "Drive",
"Drives": "Drives",
"File": "File",
"Folder": "Folder",
"Resource": "Resource",
"Name": "Name",
"Description": "Description",
"Size": "Size",
"Type": "Type",
"LastModified": "Last modified",
"Parent": "Parent",
"Path": "Path",
"Download": "Download",
"CreateDrive": "Create Drive",
"CreateFolder": "Create Folder",
"UploadFile": "Upload File",
"UploadFolder": "Upload Folder",
"EditDrive": "Edit Drive",
"RoleLabel": "Role",
"Root": "/"
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"string": {
"Storage": "Depósito"
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"string": {
"Storage": "Armazenagem"
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"string": {
"Drive": "Диск",
"Drives": "Диски",
"File": "Файл",
"Folder": "Папка",
"Resource": "Ресурс",
"Name": "Название",
"Description": "Описание",
"Size": "Размер",
"Type": "Тип",
"LastModified": "Последнее изменение",
"Parent": "Родительская папка",
"Path": "Путь",
"Download": "Скачать",
"CreateDrive": "Создать диск",
"CreateFolder": "Создать папку",
"UploadFile": "Загрузить файл",
"UploadFolder": "Загрузить папку",
"EditDrive": "Редактировать",
"RoleLabel": "Роль",
"Root": "/"
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"name": "@hcengineering/drive-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Hardcore Engineering Inc.",
"template": "@hcengineering/assets-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"test": "jest --passWithNoTests --silent",
"build:docs": "",
"format": "format src",
"build:watch": "compile",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"@types/node": "~20.11.16",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"@hcengineering/drive": "^0.6.0"
},
"repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
@@ -0,0 +1,6 @@
import { makeLocalesTest } from '@hcengineering/platform'
it(
'Locales are equale',
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
)
+27
View File
@@ -0,0 +1,27 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { loadMetadata } from '@hcengineering/platform'
import drive from '@hcengineering/drive'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(drive.icon, {
Drive: `${icons}#drive`,
File: `${icons}#file`,
Folder: `${icons}#folder`,
FolderOpen: `${icons}#folder-open`,
FolderClosed: `${icons}#folder-closed`,
Download: `${icons}#download`
})
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"types": ["node", "jest"],
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname }
}
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true,
"proseWrap": "preserve",
"plugins": [
"prettier-plugin-svelte"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "ui"
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
}
+56
View File
@@ -0,0 +1,56 @@
{
"name": "@hcengineering/drive-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"format": "format src",
"svelte-check": "do-svelte-check",
"_phase:svelte-check": "do-svelte-check",
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"svelte-loader": "^3.2.0",
"sass": "^1.53.0",
"svelte-preprocess": "^5.1.3",
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-svelte": "^2.35.1",
"prettier-plugin-svelte": "^3.2.2",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"svelte-check": "^3.6.9",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/core": "^0.6.28",
"@hcengineering/attachment": "^0.6.9",
"@hcengineering/drive": "^0.6.0",
"@hcengineering/contact-resources": "^0.6.0",
"@hcengineering/panel": "^0.6.15",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/ui": "^0.6.11",
"@hcengineering/view": "^0.6.9",
"@hcengineering/view-resources": "^0.6.0",
"svelte": "^4.2.12",
"fast-equals": "^5.0.1",
"filesize": "^8.0.3"
}
}
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}
@@ -0,0 +1,324 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { deepEqual } from 'fast-equals'
import { createEventDispatcher } from 'svelte'
import { AccountArrayEditor } from '@hcengineering/contact-resources'
import core, {
Account,
Data,
DocumentUpdate,
RolesAssignment,
Ref,
Role,
SpaceType,
generateId,
getCurrentAccount,
WithLookup
} from '@hcengineering/core'
import { Drive } from '@hcengineering/drive'
import presentation, { Card, getClient, reduceCalls } from '@hcengineering/presentation'
import { EditBox, Label, Toggle } from '@hcengineering/ui'
import { SpaceTypeSelector } from '@hcengineering/view-resources'
import driveRes from '../plugin'
export let drive: Drive | undefined = undefined
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
let name: string = drive?.name ?? ''
let description: string = drive?.description ?? ''
let isPrivate: boolean = drive?.private ?? false
let members: Ref<Account>[] =
drive?.members !== undefined ? hierarchy.clone(drive.members) : [getCurrentAccount()._id]
let owners: Ref<Account>[] = drive?.owners !== undefined ? hierarchy.clone(drive.owners) : [getCurrentAccount()._id]
let rolesAssignment: RolesAssignment = {}
let typeId: Ref<SpaceType> | undefined = drive?.type ?? driveRes.spaceType.DefaultDrive
let spaceType: WithLookup<SpaceType> | undefined
$: void loadSpaceType(typeId)
const loadSpaceType = reduceCalls(async (id: typeof typeId): Promise<void> => {
spaceType =
id !== undefined
? await client
.getModel()
.findOne(core.class.SpaceType, { _id: id }, { lookup: { _id: { roles: core.class.Role } } })
: undefined
if (drive === undefined || spaceType?.targetClass === undefined || spaceType?.$lookup?.roles === undefined) {
return
}
rolesAssignment = getRolesAssignment()
})
function getRolesAssignment (): RolesAssignment {
if (drive === undefined || spaceType?.targetClass === undefined || spaceType?.$lookup?.roles === undefined) {
return {}
}
const asMixin = hierarchy.as(drive, spaceType?.targetClass)
return spaceType.$lookup.roles.reduce<RolesAssignment>((prev, { _id }) => {
prev[_id as Ref<Role>] = (asMixin as any)[_id] ?? []
return prev
}, {})
}
async function handleSave (): Promise<void> {
if (drive === undefined) {
await createDrive()
} else {
await updateDrive()
}
}
function getDriveData (): Omit<Data<Drive>, 'type'> {
return {
name,
description,
private: isPrivate,
members,
owners,
archived: false
}
}
async function updateDrive (): Promise<void> {
if (drive === undefined || spaceType?.targetClass === undefined) {
return
}
const data = getDriveData()
const update: DocumentUpdate<Drive> = {}
if (data.name !== drive?.name) {
update.name = data.name
}
if (data.description !== drive?.description) {
update.description = data.description
}
if (data.private !== drive?.private) {
update.private = data.private
}
if (data.members.length !== drive?.members.length) {
update.members = data.members
} else {
for (const member of data.members) {
if (drive.members.findIndex((p) => p === member) === -1) {
update.members = data.members
break
}
}
}
if (data.owners?.length !== drive?.owners?.length) {
update.owners = data.owners
} else {
for (const owner of data.owners ?? []) {
if (drive.owners?.findIndex((p) => p === owner) === -1) {
update.owners = data.owners
break
}
}
}
if (Object.keys(update).length > 0) {
await client.update(drive, update)
}
if (!deepEqual(rolesAssignment, getRolesAssignment())) {
await client.updateMixin(
drive._id,
driveRes.class.Drive,
core.space.Space,
spaceType.targetClass,
rolesAssignment
)
}
close()
}
async function createDrive (): Promise<void> {
if (typeId === undefined || spaceType?.targetClass === undefined) {
return
}
const driveId = generateId<Drive>()
const driveData = getDriveData()
await client.createDoc(driveRes.class.Drive, core.space.Space, { ...driveData, type: typeId }, driveId)
// Create space type's mixin with roles assignments
await client.createMixin(driveId, driveRes.class.Drive, core.space.Space, spaceType.targetClass, rolesAssignment)
close(driveId)
}
function close (id?: Ref<Drive>): void {
dispatch('close', id)
}
function handleTypeChange (evt: CustomEvent<Ref<SpaceType>>): void {
typeId = evt.detail
}
$: roles = (spaceType?.$lookup?.roles ?? []) as Role[]
function handleOwnersChanged (newOwners: Ref<Account>[]): void {
owners = newOwners
const newMembersSet = new Set([...members, ...newOwners])
members = Array.from(newMembersSet)
}
function handleMembersChanged (newMembers: Ref<Account>[]): void {
// If a member was removed we need to remove it from any roles assignments as well
const newMembersSet = new Set(newMembers)
const removedMembersSet = new Set(members.filter((m) => !newMembersSet.has(m)))
if (removedMembersSet.size > 0 && rolesAssignment !== undefined) {
for (const [key, value] of Object.entries(rolesAssignment)) {
rolesAssignment[key as Ref<Role>] = value != null ? value.filter((m) => !removedMembersSet.has(m)) : undefined
}
}
members = newMembers
}
function handleRoleAssignmentChanged (roleId: Ref<Role>, newMembers: Ref<Account>[]): void {
if (rolesAssignment === undefined) {
rolesAssignment = {}
}
rolesAssignment[roleId] = newMembers
}
$: canSave =
name.trim().length > 0 &&
!(members.length === 0 && isPrivate) &&
typeId !== undefined &&
spaceType?.targetClass !== undefined &&
owners.length > 0 &&
(!isPrivate || owners.some((o) => members.includes(o)))
</script>
<Card
label={drive ? driveRes.string.EditDrive : driveRes.string.CreateDrive}
okLabel={drive ? presentation.string.Create : presentation.string.Save}
okAction={handleSave}
{canSave}
accentHeader
width={'medium'}
gap={'gapV-6'}
onCancel={close}
on:changeContent
>
<div class="antiGrid">
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={core.string.SpaceType} />
</div>
<SpaceTypeSelector
disabled={drive !== undefined}
descriptors={[driveRes.descriptor.DriveType]}
type={typeId}
focusIndex={4}
kind="regular"
size="large"
on:change={handleTypeChange}
/>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={core.string.Name} />
</div>
<div class="padding">
<EditBox id="teamspace-title" bind:value={name} placeholder={core.string.Name} kind={'large-style'} autoFocus />
</div>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header topAlign">
<Label label={core.string.Description} />
</div>
<div class="padding">
<EditBox id="teamspace-description" bind:value={description} placeholder={core.string.Description} />
</div>
</div>
</div>
<div class="antiGrid">
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={core.string.Owners} />
</div>
<AccountArrayEditor
value={owners}
label={core.string.Owners}
onChange={handleOwnersChanged}
kind={'regular'}
size={'large'}
/>
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header withDesciption">
<Label label={presentation.string.MakePrivate} />
<span><Label label={presentation.string.MakePrivateDescription} /></span>
</div>
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={core.string.Members} />
</div>
<AccountArrayEditor
value={members}
label={core.string.Members}
onChange={handleMembersChanged}
kind={'regular'}
size={'large'}
/>
</div>
{#each roles as role}
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={driveRes.string.RoleLabel} params={{ role: role.name }} />
</div>
<AccountArrayEditor
value={rolesAssignment?.[role._id] ?? []}
label={core.string.Members}
includeItems={members}
readonly={members.length === 0}
onChange={(refs) => {
handleRoleAssignmentChanged(role._id, refs)
}}
kind={'regular'}
size={'large'}
/>
</div>
{/each}
</div>
</Card>
@@ -0,0 +1,121 @@
<!--
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import core, { Ref, generateId } from '@hcengineering/core'
import { Drive, Folder } from '@hcengineering/drive'
import { Card, SpaceSelector, getClient } from '@hcengineering/presentation'
import { EditBox, FocusHandler, createFocusManager } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { ObjectBox } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import drive from '../plugin'
export function canClose (): boolean {
return name === ''
}
export let space: Ref<Drive>
export let parent: Ref<Folder>
const id: Ref<Folder> = generateId()
const dispatch = createEventDispatcher()
const client = getClient()
let _space = space
let _parent = parent !== drive.ids.Root ? parent : undefined
let name = ''
$: if (_space !== space) _parent = undefined
$: canSave = getTitle(name).length > 0 && _space !== undefined
function getTitle (value: string): string {
return value.trim()
}
async function create (): Promise<void> {
let path: Ref<Folder>[] = []
if (_parent != null && _parent !== drive.ids.Root) {
const parent = await client.findOne(drive.class.Folder, { _id: _parent })
if (parent === undefined) {
throw new Error('parent not found')
}
path = [parent._id, ...parent.path]
}
await client.createDoc(
drive.class.Folder,
space,
{
name: getTitle(name),
parent: _parent ?? drive.ids.Root,
path
},
id
)
dispatch('close', id)
}
const manager = createFocusManager()
</script>
<FocusHandler {manager} />
<Card
label={drive.string.CreateFolder}
okAction={create}
{canSave}
on:close={() => {
dispatch('close')
}}
on:changeContent
>
<svelte:fragment slot="header">
<SpaceSelector
_class={drive.class.Drive}
label={drive.string.Drive}
bind:space={_space}
kind={'regular'}
size={'small'}
iconWithEmoji={view.ids.IconWithEmoji}
defaultIcon={drive.icon.Drive}
/>
<ObjectBox
_class={drive.class.Folder}
bind:value={_parent}
docQuery={{
space: _space
}}
kind={'regular'}
size={'small'}
label={drive.string.Root}
searchField={'name'}
allowDeselect
showNavigate={false}
docProps={{ disabled: true, noUnderline: true }}
on:object={(evt) => {
console.log('selected', evt)
}}
/>
</svelte:fragment>
<div class="flex-row-center clear-mins">
<EditBox placeholder={core.string.Name} bind:value={name} kind={'large-style'} autoFocus focusIndex={1} />
</div>
</Card>
@@ -0,0 +1,53 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { type Drive } from '@hcengineering/drive'
import { Button, IconDetails, IconMoreH } from '@hcengineering/ui'
import { showMenu } from '@hcengineering/view-resources'
import DrivePresenter from './DrivePresenter.svelte'
export let object: Drive
export let asideShown: boolean = false
</script>
<div class="popupPanel-title">
<div class="popupPanel-title__content">
<div class="title">
<DrivePresenter value={object} shouldShowAvatar={false} disabled noUnderline />
</div>
</div>
<div class="flex-row-center ml-3 no-print">
<Button
icon={IconMoreH}
iconProps={{ size: 'medium' }}
kind={'icon'}
on:click={(ev) => {
showMenu(ev, { object })
}}
/>
<div class="buttons-divider max-h-7 h-7 mx-2 no-print" />
<Button
focusIndex={10008}
icon={IconDetails}
iconProps={{ size: 'medium', filled: asideShown }}
kind={'icon'}
selected={asideShown}
on:click={() => {
asideShown = !asideShown
}}
/>
</div>
</div>
@@ -0,0 +1,63 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { type Ref } from '@hcengineering/core'
import drive, { type Drive } from '@hcengineering/drive'
import { createQuery } from '@hcengineering/presentation'
import { Separator } from '@hcengineering/ui'
import { DocAttributeBar } from '@hcengineering/view-resources'
import DriveHeader from './DriveHeader.svelte'
import FolderBrowser from './FolderBrowser.svelte'
export let _id: Ref<Drive>
export let readonly: boolean = false
export let embedded: boolean = false
export let kind: 'default' | 'modern' = 'default'
export function canClose (): boolean {
return false
}
let object: Drive | undefined = undefined
let asideShown = false
const query = createQuery()
$: query.query(drive.class.Drive, { _id }, (res) => {
;[object] = res
})
</script>
{#if object}
<div class="popupPanel panel {kind}" class:embedded>
<DriveHeader {object} bind:asideShown />
<div class="popupPanel-body">
<div class="popupPanel-body__main">
<FolderBrowser space={object._id} parent={drive.ids.Root} />
</div>
{#if asideShown}
<Separator name="aside" float={false} index={0} />
<div class="popupPanel-body__aside no-print" class:shown={asideShown}>
<Separator name={'panel-aside'} float={true} index={0} />
<div class="antiPanel-wrap__content">
<DocAttributeBar {object} {readonly} ignoreKeys={[]} />
</div>
</div>
{/if}
</div>
</div>
{/if}
@@ -0,0 +1,56 @@
<!--
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { Drive } from '@hcengineering/drive'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { ObjectPresenterType } from '@hcengineering/view'
import drive from '../plugin'
export let value: Drive
export let inline: boolean = false
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let shouldShowAvatar = true
export let type: ObjectPresenterType = 'link'
</script>
{#if value}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
{:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{#if shouldShowAvatar}
<div class="icon">
<Icon icon={drive.icon.Drive} size={'small'} />
</div>
{/if}
<span class="label no-underline nowrap" class:fs-bold={accent}>
{value.name}
</span>
</div>
</DocNavLink>
{:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}
</span>
{/if}
{/if}
@@ -0,0 +1,147 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { AccountRole, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { type Drive } from '@hcengineering/drive'
import { createQuery } from '@hcengineering/presentation'
import { Button, ButtonWithDropdown, IconAdd, IconDropdown, Loading, SelectPopupValueType } from '@hcengineering/ui'
import drive from '../plugin'
import { getFolderIdFromFragment } from '../navigation'
import { createDrive, createFolder, createFiles } from '../utils'
export let currentSpace: Ref<Drive> | undefined
export let currentFragment: string | undefined
const query = createQuery()
let loading = true
let hasDrive = false
query.query(
drive.class.Drive,
{ archived: false },
(res) => {
hasDrive = res.length > 0
loading = false
},
{ limit: 1, projection: { _id: 1 } }
)
$: parent = getFolderIdFromFragment(currentFragment ?? '') ?? drive.ids.Root
async function handleDropdownItemSelected (res?: SelectPopupValueType['id']): Promise<void> {
if (res === drive.string.CreateDrive) {
await handleCreateDrive()
} else if (res === drive.string.CreateFolder) {
await handleCreateFolder()
} else if (res === drive.string.UploadFile) {
await handleUploadFile()
}
}
async function handleCreateDrive (): Promise<void> {
await createDrive()
}
async function handleCreateFolder (): Promise<void> {
if (currentSpace !== undefined) {
await createFolder(currentSpace, parent, true)
}
}
async function handleUploadFile (): Promise<void> {
if (currentSpace !== undefined) {
inputFile.click()
}
}
const dropdownItems = hasAccountRole(getCurrentAccount(), AccountRole.User)
? [
{ id: drive.string.CreateDrive, label: drive.string.CreateDrive, icon: drive.icon.Drive },
{ id: drive.string.CreateFolder, label: drive.string.CreateFolder, icon: drive.icon.Folder },
{ id: drive.string.UploadFile, label: drive.string.UploadFile, icon: drive.icon.File }
// { id: drive.string.UploadFolder, label: drive.string.UploadFolder }
]
: [
{ id: drive.string.CreateFolder, label: drive.string.CreateFolder, icon: drive.icon.Folder },
{ id: drive.string.UploadFile, label: drive.string.UploadFile, icon: drive.icon.File }
// { id: drive.string.UploadFolder, label: drive.string.UploadFolder }
]
let progress = false
let inputFile: HTMLInputElement
async function fileSelected (): Promise<void> {
if (currentSpace === undefined) {
return
}
const list = inputFile.files
if (list === null || list.length === 0) {
return
}
progress = true
await createFiles(list, currentSpace, parent)
inputFile.value = ''
progress = false
}
</script>
{#if loading}
<Loading shrink />
{:else}
<input
bind:this={inputFile}
multiple
type="file"
name="file"
id="file"
style="display: none"
on:change={fileSelected}
/>
<div class="antiNav-subheader">
{#if hasDrive}
<ButtonWithDropdown
icon={IconAdd}
justify={'left'}
kind={'primary'}
label={drive.string.UploadFile}
mainButtonId={'new-document'}
dropdownIcon={IconDropdown}
{dropdownItems}
disabled={currentSpace === undefined || progress}
on:click={handleUploadFile}
on:dropdown-selected={(ev) => {
void handleDropdownItemSelected(ev.detail)
}}
/>
{:else}
<Button
icon={IconAdd}
label={drive.string.CreateDrive}
justify={'left'}
width={'100%'}
kind={'primary'}
gap={'large'}
disabled={progress}
on:click={handleCreateDrive}
/>
{/if}
</div>
{/if}
@@ -0,0 +1,119 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Doc, Ref, SortingOrder, Space } from '@hcengineering/core'
import { Drive, Folder } from '@hcengineering/drive'
import { createQuery } from '@hcengineering/presentation'
import { Action, navigate } from '@hcengineering/ui'
import drive from '../plugin'
import { getDriveLink, getFolderIdFromFragment, getFolderLink } from '../navigation'
import FolderTreeLevel from './FolderTreeLevel.svelte'
import FolderTreeElement from './FolderTreeElement.svelte'
export let space: Drive
export let currentSpace: Ref<Space> | undefined
export let currentSpecial: string | undefined
export let currentFragment: string | undefined
export let getActions: (space: Space) => Promise<Action[]> = async () => []
let folders: Ref<Folder>[] = []
let folderById: Map<Ref<Folder>, Folder> = new Map<Ref<Folder>, Folder>()
let descendants: Map<Ref<Folder>, Folder[]> = new Map<Ref<Folder>, Folder[]>()
function getDescendants (obj: Ref<Folder>): Ref<Folder>[] {
return (descendants.get(obj) ?? []).sort((a, b) => a.name.localeCompare(b.name)).map((p) => p._id)
}
let selected: Ref<Doc> | undefined
$: selected = getFolderIdFromFragment(currentFragment ?? '')
const query = createQuery()
query.query(
drive.class.Folder,
{
space: space._id
},
(result) => {
folderById.clear()
descendants.clear()
for (const doc of result) {
const current = descendants.get(doc.parent) ?? []
current.push(doc)
descendants.set(doc.parent, current)
folderById.set(doc._id, doc)
}
folderById = folderById
descendants = descendants
folders = getDescendants(drive.ids.Root)
},
{
sort: {
name: SortingOrder.Ascending
}
}
)
function handleDriveSelected (_id: Ref<Drive>): void {
navigate(getDriveLink(_id))
}
function handleFolderSelected (_id: Ref<Folder>): void {
navigate(getFolderLink(_id))
}
</script>
{#if space}
<FolderTreeElement
_id={space._id}
icon={drive.icon.Drive}
title={space.name}
selected={currentSpace === space._id && selected === space._id}
parent={descendants.size > 0}
actions={() => getActions(space)}
on:click={() => {
handleDriveSelected(space._id)
}}
>
<!-- <TreeNode
{collapsed}
_id={space._id}
icon={drive.icon.Drive}
title={space.name}
folder
parent={descendants.size > 0}
actions={() => getActions(space)}
on:click={() => {
if (selected === space?._id) {
collapsed = !collapsed
} else {
handleDriveSelected(space._id)
}
}}
> -->
<FolderTreeLevel
{folders}
{descendants}
{folderById}
{selected}
on:selected={(ev) => {
handleFolderSelected(ev.detail)
}}
/>
<!-- </TreeNode> -->
</FolderTreeElement>
{/if}
@@ -0,0 +1,26 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { type Folder } from '@hcengineering/drive'
import FolderBrowser from './FolderBrowser.svelte'
export let object: Folder
export let readonly: boolean = false
</script>
{#if object}
<FolderBrowser space={object.space} parent={object._id} {readonly} />
{/if}
@@ -0,0 +1,56 @@
<!--
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { File } from '@hcengineering/drive'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { ObjectPresenterType } from '@hcengineering/view'
import drive from '../plugin'
export let value: File
export let inline: boolean = false
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let shouldShowAvatar = true
export let type: ObjectPresenterType = 'link'
</script>
{#if value}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
{:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{#if shouldShowAvatar}
<div class="icon">
<Icon icon={drive.icon.File} size={'small'} />
</div>
{/if}
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name}
</span>
</div>
</DocNavLink>
{:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}
</span>
{/if}
{/if}
@@ -0,0 +1,26 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import filesize from 'filesize'
export let value: number | undefined
export let accent: boolean = false
</script>
{#if value}
<span class="overflow-label select-text" class:fs-bold={accent}>
{value !== undefined ? filesize(value) : '-'}
</span>
{/if}
@@ -0,0 +1,105 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { type Doc, type DocumentQuery, type Ref, type WithLookup } from '@hcengineering/core'
import drive, { type Drive, type Folder } from '@hcengineering/drive'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import { Scroller, SearchEdit } from '@hcengineering/ui'
import {
FilterBar,
FilterButton,
ViewletContentView,
ViewletSelector,
ViewletSettingButton
} from '@hcengineering/view-resources'
import { createFiles } from '../utils'
export let space: Ref<Drive>
export let parent: Ref<Folder>
export let readonly: boolean = false
const _class = drive.class.Resource
$: query = { space, parent }
let dragover = false
let viewlet: WithLookup<Viewlet> | undefined = undefined
let viewOptions: ViewOptions | undefined
let search = ''
let searchQuery: DocumentQuery<Doc> = { ...query }
let resultQuery: DocumentQuery<Doc> = { ...searchQuery }
$: if (query !== undefined) updateSearchQuery(search)
$: resultQuery = { ...searchQuery }
function updateSearchQuery (search: string): void {
searchQuery = search === '' ? { ...query } : { ...query, $search: search }
}
async function handleDrop (e: DragEvent): Promise<void> {
if (readonly) {
return
}
// progress = true
const list = e.dataTransfer?.files
if (list !== undefined && list.length !== 0) {
await createFiles(list, space, parent)
}
// progress = false
}
</script>
{#if space !== undefined}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="antiComponent"
class:solid={dragover}
on:dragover|preventDefault={() => {
dragover = true
}}
on:dragleave={() => {
dragover = false
}}
on:drop|preventDefault|stopPropagation={(ev) => {
void handleDrop(ev)
}}
>
<div class="ac-header full divide caption-height">
<div class="ac-header-full small-gap">
<SearchEdit bind:value={search} on:change={() => {}} />
<div class="buttons-divider" />
<FilterButton {_class} {space} />
</div>
<div class="ac-header-full medium-gap">
<ViewletSettingButton bind:viewOptions bind:viewlet />
<ViewletSelector bind:viewlet viewletQuery={{ attachTo: _class }} />
</div>
</div>
{#if viewlet !== undefined && viewOptions}
<FilterBar {_class} {space} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
<div class="popupPanel rowContent">
{#if viewlet}
<Scroller horizontal={true}>
<ViewletContentView {_class} {viewlet} query={resultQuery} {space} {viewOptions} />
</Scroller>
{/if}
</div>
{/if}
</div>
{/if}
@@ -0,0 +1,81 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { toIdMap, type Doc, type Ref } from '@hcengineering/core'
import drive, { type Folder } from '@hcengineering/drive'
import { getClient } from '@hcengineering/presentation'
import { Button, IconDetails, IconMoreH } from '@hcengineering/ui'
import { DocsNavigator, showMenu } from '@hcengineering/view-resources'
import FolderPresenter from './FolderPresenter.svelte'
export let object: Folder
export let asideShown: boolean = false
const client = getClient()
let parents: Doc[] = []
$: void updateParents(object.path)
async function updateParents (path: Ref<Folder>[]): Promise<void> {
const docs: Array<Doc> = []
const folders = await client.findAll(drive.class.Folder, { _id: { $in: path } })
const byId = toIdMap(folders)
for (const p of path) {
const parent = byId.get(p)
if (parent !== undefined) {
docs.push(parent)
}
}
const root = await client.findOne(drive.class.Drive, { _id: object.space })
if (root !== undefined) {
docs.push(root)
}
parents = docs.reverse()
}
</script>
<div class="popupPanel-title">
<div class="popupPanel-title__content">
<DocsNavigator elements={parents} />
<div class="title">
<FolderPresenter value={object} shouldShowAvatar={false} disabled noUnderline />
</div>
</div>
<div class="flex-row-center ml-3 no-print">
<Button
icon={IconMoreH}
iconProps={{ size: 'medium' }}
kind={'icon'}
on:click={(ev) => {
showMenu(ev, { object })
}}
/>
<div class="buttons-divider max-h-7 h-7 mx-2 no-print" />
<Button
focusIndex={10008}
icon={IconDetails}
iconProps={{ size: 'medium', filled: asideShown }}
kind={'icon'}
selected={asideShown}
on:click={() => {
asideShown = !asideShown
}}
/>
</div>
</div>
@@ -0,0 +1,63 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { type Ref } from '@hcengineering/core'
import drive, { type Folder } from '@hcengineering/drive'
import { createQuery } from '@hcengineering/presentation'
import { Separator } from '@hcengineering/ui'
import { DocAttributeBar } from '@hcengineering/view-resources'
import FolderHeader from './FolderHeader.svelte'
import FolderBrowser from './FolderBrowser.svelte'
export let _id: Ref<Folder>
export let readonly: boolean = false
export let embedded: boolean = false
export let kind: 'default' | 'modern' = 'default'
export function canClose (): boolean {
return false
}
let object: Folder | undefined = undefined
let asideShown = false
const query = createQuery()
$: query.query(drive.class.Folder, { _id }, (res) => {
;[object] = res
})
</script>
{#if object}
<div class="popupPanel panel {kind}" class:embedded>
<FolderHeader {object} bind:asideShown />
<div class="popupPanel-body">
<div class="popupPanel-body__main">
<FolderBrowser space={object.space} parent={object._id} {readonly} />
</div>
{#if asideShown}
<Separator name="aside" float={false} index={0} />
<div class="popupPanel-body__aside no-print" class:shown={asideShown}>
<Separator name={'panel-aside'} float={true} index={0} />
<div class="antiPanel-wrap__content">
<DocAttributeBar {object} {readonly} ignoreKeys={[]} />
</div>
</div>
{/if}
</div>
</div>
{/if}
@@ -0,0 +1,56 @@
<!--
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { Folder } from '@hcengineering/drive'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { Icon, tooltip } from '@hcengineering/ui'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { ObjectPresenterType } from '@hcengineering/view'
import drive from '../plugin'
export let value: Folder
export let inline: boolean = false
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let shouldShowAvatar = true
export let type: ObjectPresenterType = 'link'
</script>
{#if value}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} />
{:else if type === 'link'}
<DocNavLink {disabled} object={value} {accent} {noUnderline}>
<div class="flex-presenter" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{#if shouldShowAvatar}
<div class="icon">
<Icon icon={drive.icon.Folder} size={'small'} />
</div>
{/if}
<span class="label nowrap" class:no-underline={noUnderline || disabled} class:fs-bold={accent}>
{value.name}
</span>
</div>
</DocNavLink>
{:else if type === 'text'}
<span class="overflow-label" use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}
</span>
{/if}
{/if}
@@ -0,0 +1,101 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import type { Doc, Ref } from '@hcengineering/core'
import { type Asset, type IntlString, getEmbeddedLabel } from '@hcengineering/platform'
import { type Action, type AnySvelteComponent, tooltip } from '@hcengineering/ui'
import {
Icon,
IconChevronDown,
IconMoreH,
Label,
Menu,
getTreeCollapsed,
setTreeCollapsed,
showPopup
} from '@hcengineering/ui'
export let _id: Ref<Doc>
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let iconProps: Record<string, any> | undefined = undefined
export let label: IntlString | undefined = undefined
export let title: string | undefined = undefined
export let parent: boolean = false
export let collapsed: boolean = getTreeCollapsed(_id)
export let selected: boolean = false
export let level: number = 0
export let actions: (originalEvent?: MouseEvent) => Promise<Action[]> = async () => []
let hovered = false
async function onMenuClick (ev: MouseEvent): Promise<void> {
showPopup(Menu, { actions: await actions(ev), ctx: _id }, ev.target as HTMLElement, () => {
hovered = false
})
hovered = true
}
const dispatch = createEventDispatcher()
$: collapsed = getTreeCollapsed(_id)
$: setTreeCollapsed(_id, collapsed)
$: folder = level === 0
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="antiNav-element tree"
class:selected
class:hovered
class:parent
class:collapsed
style:padding-left={`${level > 0 ? level * 1.25 + 0.125 : 0.75}rem`}
on:click={() => {
if (selected) {
collapsed = !collapsed
}
dispatch('click')
}}
>
{#if !folder}
<div
class="an-element__tool arrow"
class:empty={!parent}
on:click|preventDefault|stopPropagation={() => {
if (parent) collapsed = !collapsed
}}
>
{#if parent}<IconChevronDown size={'small'} filled />{/if}
</div>
{/if}
{#if icon}
<div class="an-element__icon" class:folder>
<Icon {icon} {iconProps} size={'small'} />
</div>
{/if}
<span class="an-element__label" use:tooltip={{ label: label ?? getEmbeddedLabel(title ?? '') }}>
{#if label}<Label {label} />{:else}{title}{/if}
</span>
<div class="an-element__grow" />
<div class="an-element__tool" class:pressed={hovered} on:click|preventDefault|stopPropagation={onMenuClick}>
<IconMoreH size={'small'} />
</div>
</div>
{#if parent && !collapsed}
<div class="antiNav-element__dropbox"><slot /></div>
{/if}
@@ -0,0 +1,87 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Doc, Ref } from '@hcengineering/core'
import { Folder } from '@hcengineering/drive'
import { getResource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Action, IconEdit } from '@hcengineering/ui'
import { getActions as getContributedActions } from '@hcengineering/view-resources'
import FolderTreeElement from './FolderTreeElement.svelte'
import drive from '../plugin'
export let folders: Ref<Folder>[]
export let folderById: Map<Ref<Folder>, Folder>
export let descendants: Map<Ref<Folder>, Folder[]>
export let selected: Ref<Doc> | undefined
export let level = 1
const client = getClient()
const dispatch = createEventDispatcher()
function getDescendants (obj: Ref<Folder>): Ref<Folder>[] {
return (descendants.get(obj) ?? []).sort((a, b) => a.name.localeCompare(b.name)).map((p) => p._id)
}
async function getActions (obj: Folder): Promise<Action[]> {
const result: Action[] = []
const extraActions = await getContributedActions(client, obj)
for (const act of extraActions) {
result.push({
icon: act.icon ?? IconEdit,
label: act.label,
action: async (ctx: any, evt: Event) => {
const impl = await getResource(act.action)
await impl(obj, evt, act.actionProps)
}
})
}
return result
}
function handleSelected (obj: Ref<Folder>): void {
dispatch('selected', obj)
}
$: _folders = folders.map((it) => folderById.get(it)).filter((it) => it !== undefined) as Folder[]
$: _descendants = new Map(_folders.map((it) => [it._id, getDescendants(it._id)]))
</script>
{#each _folders as doc}
{@const desc = _descendants.get(doc._id) ?? []}
{#if doc}
<FolderTreeElement
_id={doc._id}
icon={drive.icon.Folder}
title={doc.name}
selected={selected === doc._id}
parent={desc.length > 0}
actions={async () => await getActions(doc)}
{level}
on:click={() => {
handleSelected(doc._id)
}}
>
{#if desc.length}
<svelte:self folders={desc} {descendants} {folderById} {selected} level={level + 1} on:selected />
{/if}
</FolderTreeElement>
{/if}
{/each}
@@ -0,0 +1,18 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
</script>
<div>Here will be grid view</div>
@@ -0,0 +1,53 @@
<!--
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
-->
<script lang="ts">
import { type File, type Folder, type Resource } from '@hcengineering/drive'
import { getClient } from '@hcengineering/presentation'
import { ObjectPresenterType } from '@hcengineering/view'
import drive from '../plugin'
import FilePresenter from './FilePresenter.svelte'
import FolderPresenter from './FolderPresenter.svelte'
export let value: Resource
export let inline: boolean = false
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let shouldShowAvatar = true
export let type: ObjectPresenterType = 'link'
const client = getClient()
const hierarchy = client.getHierarchy()
function isFile (value: Resource): value is File {
return hierarchy.isDerived(value._class, drive.class.File)
}
function isFolder (value: Resource): value is Folder {
return hierarchy.isDerived(value._class, drive.class.Folder)
}
</script>
{#if value}
{#if isFile(value)}
<FilePresenter {value} {inline} {disabled} {accent} {noUnderline} {shouldShowAvatar} {type} />
{:else if isFolder(value)}
<FolderPresenter {value} {inline} {disabled} {accent} {noUnderline} {shouldShowAvatar} {type} />
{/if}
{/if}
+94
View File
@@ -0,0 +1,94 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Doc, type Ref } from '@hcengineering/core'
import drive, { type Drive, type File, type Folder } from '@hcengineering/drive'
import { type Resources } from '@hcengineering/platform'
import { getFileUrl } from '@hcengineering/presentation'
import { type Location, showPopup } from '@hcengineering/ui'
import CreateDrive from './components/CreateDrive.svelte'
import DrivePanel from './components/DrivePanel.svelte'
import DriveSpaceHeader from './components/DriveSpaceHeader.svelte'
import DriveSpacePresenter from './components/DriveSpacePresenter.svelte'
import DrivePresenter from './components/DrivePresenter.svelte'
import EditFolder from './components/EditFolder.svelte'
import FilePresenter from './components/FilePresenter.svelte'
import FileSizePresenter from './components/FileSizePresenter.svelte'
import FolderPanel from './components/FolderPanel.svelte'
import FolderPresenter from './components/FolderPresenter.svelte'
import GridView from './components/GridView.svelte'
import ResourcePresenter from './components/ResourcePresenter.svelte'
import { getDriveLink, getFolderLink, resolveLocation } from './navigation'
import { createFolder } from './utils'
async function CreateRootFolder (doc: Drive): Promise<void> {
await createFolder(doc._id, drive.ids.Root)
}
async function CreateChildFolder (doc: Folder): Promise<void> {
await createFolder(doc.space, doc._id)
}
async function EditDrive (drive: Drive): Promise<void> {
showPopup(CreateDrive, { drive })
}
async function DownloadFile (doc: File | File[]): Promise<void> {
const files = Array.isArray(doc) ? doc : [doc]
for (const file of files) {
const url = getFileUrl(file.file, 'full', file.name)
window.open(url, '_blank')
}
}
async function DriveLinkProvider (doc: Doc): Promise<Location> {
return getDriveLink(doc._id as Ref<Drive>)
}
async function FolderLinkProvider (doc: Doc): Promise<Location> {
return getFolderLink(doc._id as Ref<Folder>)
}
export default async (): Promise<Resources> => ({
component: {
CreateDrive,
DrivePanel,
DriveSpaceHeader,
DriveSpacePresenter,
DrivePresenter,
EditFolder,
FilePresenter,
FileSizePresenter,
FolderPanel,
FolderPresenter,
GridView,
ResourcePresenter
},
actionImpl: {
CreateChildFolder,
CreateRootFolder,
EditDrive,
DownloadFile
},
function: {
DriveLinkProvider,
FolderLinkProvider
},
resolver: {
Location: resolveLocation
}
})
+121
View File
@@ -0,0 +1,121 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Doc, Ref } from '@hcengineering/core'
import drive, { type Drive, type Folder, driveId } from '@hcengineering/drive'
import { getClient } from '@hcengineering/presentation'
import { type Location, type ResolvedLocation, getCurrentResolvedLocation, getPanelURI } from '@hcengineering/ui'
import view, { type ObjectPanel } from '@hcengineering/view'
export function getPanelFragment<T extends Doc> (object: Pick<T, '_class' | '_id'>): string {
const hierarchy = getClient().getHierarchy()
const objectPanelMixin = hierarchy.classHierarchyMixin<Doc, ObjectPanel>(object._class, view.mixin.ObjectPanel)
const component = objectPanelMixin?.component ?? view.component.EditDoc
return getPanelURI(component, object._id, object._class, 'content')
}
export function getFolderIdFromFragment (fragment: string): Ref<Folder> | undefined {
const [, _id] = decodeURIComponent(fragment).split('|')
return _id as Ref<Folder>
}
export function getDriveLink (_id: Ref<Drive>): Location {
const loc = getCurrentResolvedLocation()
loc.path.length = 2
loc.fragment = undefined
loc.query = undefined
loc.path[2] = driveId
loc.path[3] = _id
return loc
}
export function getFolderLink (_id: Ref<Folder>): Location {
const loc = getCurrentResolvedLocation()
loc.path.length = 2
loc.fragment = undefined
loc.query = undefined
loc.path[2] = driveId
loc.path[3] = 'folder'
loc.path[4] = _id
return loc
}
export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> {
if (loc.path[2] !== driveId) {
return undefined
}
if (loc.path[3] === 'folder' && loc.path[4] !== undefined) {
const folderId = loc.path[4] as Ref<Folder>
return await generateFolderLocation(loc, folderId)
}
if (loc.path[3] !== undefined) {
const driveId = loc.path[3] as Ref<Drive>
return await generateDriveLocation(loc, driveId)
}
return undefined
}
export async function generateFolderLocation (loc: Location, id: Ref<Folder>): Promise<ResolvedLocation | undefined> {
const client = getClient()
const doc = await client.findOne(drive.class.Folder, { _id: id })
if (doc === undefined) {
console.error(`Could not find folder ${id}.`)
return undefined
}
const appComponent = loc.path[0] ?? ''
const workspace = loc.path[1] ?? ''
return {
loc: {
path: [appComponent, workspace, driveId, doc.space],
fragment: getPanelFragment(doc)
},
defaultLocation: {
path: [appComponent, workspace, driveId],
fragment: getPanelFragment(doc)
}
}
}
export async function generateDriveLocation (loc: Location, id: Ref<Drive>): Promise<ResolvedLocation | undefined> {
const client = getClient()
const doc = await client.findOne(drive.class.Drive, { _id: id })
if (doc === undefined) {
console.error(`Could not find drive ${id}.`)
return undefined
}
const appComponent = loc.path[0] ?? ''
const workspace = loc.path[1] ?? ''
return {
loc: {
path: [appComponent, workspace, driveId, id],
fragment: getPanelFragment(doc)
},
defaultLocation: {
path: [appComponent, workspace, driveId],
fragment: getPanelFragment(doc)
}
}
}
+30
View File
@@ -0,0 +1,30 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type IntlString, mergeIds } from '@hcengineering/platform'
import drive, { driveId } from '@hcengineering/drive'
export default mergeIds(driveId, drive, {
component: {},
string: {
CreateDrive: '' as IntlString,
CreateFolder: '' as IntlString,
UploadFile: '' as IntlString,
UploadFolder: '' as IntlString,
EditDrive: '' as IntlString,
RoleLabel: '' as IntlString,
Root: '' as IntlString
}
})
+85
View File
@@ -0,0 +1,85 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import attachment from '@hcengineering/attachment'
import { type Blob, type Class, type Doc, type Ref } from '@hcengineering/core'
import drive, { type Drive, type Folder } from '@hcengineering/drive'
import { getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { showPopup } from '@hcengineering/ui'
import { openDoc } from '@hcengineering/view-resources'
import CreateDrive from './components/CreateDrive.svelte'
import CreateFolder from './components/CreateFolder.svelte'
async function navigateToDoc (_id: Ref<Doc>, _class: Ref<Class<Doc>>): Promise<void> {
const client = getClient()
const doc = await client.findOne(_class, { _id })
if (doc !== undefined) {
void openDoc(client.getHierarchy(), doc)
}
}
export async function createFolder (space: Ref<Drive>, parent: Ref<Folder>, open = false): Promise<void> {
showPopup(CreateFolder, { space, parent }, 'top', async (id) => {
if (open && id !== undefined && id !== null) {
await navigateToDoc(id, drive.class.Folder)
}
})
}
export async function createDrive (open = false): Promise<void> {
showPopup(CreateDrive, {}, 'top', async (id) => {
if (open && id !== undefined && id !== null) {
await navigateToDoc(id, drive.class.Folder)
}
})
}
export async function editDrive (drive: Drive): Promise<void> {
showPopup(CreateDrive, { drive })
}
export async function createFiles (list: FileList, space: Ref<Drive>, parent: Ref<Folder>): Promise<void> {
const client = getClient()
const folder = await client.findOne(drive.class.Folder, { space, _id: parent })
for (let index = 0; index < list.length; index++) {
const file = list.item(index)
if (file !== null) {
await createFile(file, space, folder)
}
}
}
export async function createFile (file: File, space: Ref<Drive>, parent: Folder | undefined): Promise<void> {
const client = getClient()
try {
const uploadFile = await getResource(attachment.helper.UploadFile)
const uuid = await uploadFile(file)
// TODO obtain metadata
// const metadata = await getAttachmentMetadata(file, uuid)
await client.createDoc(drive.class.File, space, {
name: file.name,
file: uuid as Ref<Blob>,
parent: parent?._id ?? drive.ids.Root,
path: parent !== undefined ? [parent._id, ...parent.path] : []
})
} catch (e) {
void setPlatformStatus(unknownError(e))
}
}
+5
View File
@@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+44
View File
@@ -0,0 +1,44 @@
{
"name": "@hcengineering/drive",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/preference": "^0.6.9",
"@hcengineering/ui": "^0.6.11"
},
"repository": "https://github.com/hcengineering/anticrm",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
+21
View File
@@ -0,0 +1,21 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { driveId, drivePlugin } from './plugin'
export * from './types'
export { driveId }
export default drivePlugin
+71
View File
@@ -0,0 +1,71 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Class, Doc, Mixin, Ref, SpaceType, SpaceTypeDescriptor, Type } from '@hcengineering/core'
import type { Asset, IntlString, Plugin, Resource as PlatformResource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { Location, ResolvedLocation } from '@hcengineering/ui'
import { Drive, File, FileSize, Folder, Resource } from './types'
export * from './types'
/**
* @public
*/
export const driveId = 'drive' as Plugin
export const drivePlugin = plugin(driveId, {
class: {
Drive: '' as Ref<Class<Drive>>,
File: '' as Ref<Class<File>>,
Folder: '' as Ref<Class<Folder>>,
Resource: '' as Ref<Class<Resource>>,
TypeFileSize: '' as Ref<Class<Type<FileSize>>>
},
mixin: {
DefaultDriveTypeData: '' as Ref<Mixin<Drive>>
},
icon: {
Drive: '' as Asset,
File: '' as Asset,
Folder: '' as Asset,
FolderOpen: '' as Asset,
FolderClosed: '' as Asset,
Download: '' as Asset
},
app: {
Drive: '' as Ref<Doc>
},
ids: {
Root: '' as Ref<Folder>
},
resolver: {
Location: '' as PlatformResource<(loc: Location) => Promise<ResolvedLocation | undefined>>
},
string: {
Drive: '' as IntlString,
File: '' as IntlString,
Folder: '' as IntlString,
Resource: '' as IntlString
},
descriptor: {
DriveType: '' as Ref<SpaceTypeDescriptor>
},
spaceType: {
DefaultDrive: '' as Ref<SpaceType>
}
})
export default drivePlugin
+47
View File
@@ -0,0 +1,47 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Blob, Doc, Ref, TypedSpace } from '@hcengineering/core'
/** @public */
export type FileSize = number
/** @public */
export interface Drive extends TypedSpace {}
/** @public */
export interface Resource extends Doc<Drive> {
name: string
file?: Ref<Blob>
parent: Ref<Resource>
path: Ref<Resource>[]
}
/** @public */
export interface Folder extends Resource {
parent: Ref<Folder>
path: Ref<Folder>[]
file?: undefined
}
/** @public */
export interface File extends Resource {
file: Ref<Blob>
parent: Ref<Folder>
path: Ref<Folder>[]
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
@@ -0,0 +1,58 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Doc } from '@hcengineering/core'
import { Breadcrumbs, BreadcrumbsModel, getClient } from '@hcengineering/presentation'
import { AttributeModel } from '@hcengineering/view'
import { getObjectPresenter, restrictionStore } from '../utils'
export let elements: readonly Doc[]
const client = getClient()
async function getBreadcrumbsModels (elements: readonly Doc[]): Promise<readonly BreadcrumbsModel[]> {
if (elements.length === 0) {
return []
}
const models: BreadcrumbsModel[] = []
for (const element of elements) {
const attributeModel: AttributeModel | undefined = await getObjectPresenter(client, element._class, { key: '' })
if (attributeModel) {
const breadcrumbsModel: BreadcrumbsModel = {
component: attributeModel.presenter,
props: {
shouldShowAvatar: false,
noUnderline: true,
noSelect: true,
...(attributeModel.props ?? {}),
value: element
}
}
models.push(breadcrumbsModel)
}
}
return models
}
</script>
{#await getBreadcrumbsModels(elements) then models}
{#if models.length > 0}
<Breadcrumbs {models} disabled={$restrictionStore.disableNavigation} />
{/if}
{/await}
@@ -14,15 +14,19 @@
-->
<script lang="ts">
import { AttachedDoc, Doc } from '@hcengineering/core'
import { Breadcrumbs, BreadcrumbsModel, getClient } from '@hcengineering/presentation'
import { getObjectPresenter, isAttachedDoc, restrictionStore } from '../utils'
import { AttributeModel } from '@hcengineering/view'
import { getClient } from '@hcengineering/presentation'
import { isAttachedDoc } from '../utils'
import DocsNavigator from './DocsNavigator.svelte'
export let element: Doc | AttachedDoc
const client = getClient()
async function getParents (doc: AttachedDoc): Promise<readonly Doc[]> {
async function getParents (doc: Doc | AttachedDoc): Promise<readonly Doc[]> {
if (!isAttachedDoc(doc)) {
return []
}
const parents: Doc[] = []
let currentDoc: Doc | undefined = doc
@@ -40,43 +44,8 @@
return parents.reverse()
}
async function getBreadcrumbsModels (doc: typeof element): Promise<readonly BreadcrumbsModel[]> {
if (!isAttachedDoc(doc)) {
return []
}
const parents = await getParents(doc)
if (parents.length === 0) {
return []
}
const models: BreadcrumbsModel[] = []
for (const parent of parents) {
const attributeModel: AttributeModel | undefined = await getObjectPresenter(client, parent._class, { key: '' })
if (attributeModel) {
const breadcrumbsModel: BreadcrumbsModel = {
component: attributeModel.presenter,
props: {
shouldShowAvatar: false,
noUnderline: true,
noSelect: true,
...(attributeModel.props ?? {}),
value: parent
}
}
models.push(breadcrumbsModel)
}
}
return models
}
</script>
{#await getBreadcrumbsModels(element) then models}
{#if models.length > 0}
<Breadcrumbs {models} disabled={$restrictionStore.disableNavigation} />
{/if}
{#await getParents(element) then parents}
<DocsNavigator elements={parents} />
{/await}
+1
View File
@@ -133,6 +133,7 @@ export { getActions, getContextActions, invokeAction, showMenu } from './actions
export { default as ActionButton } from './components/ActionButton.svelte'
export { default as ActionHandler } from './components/ActionHandler.svelte'
export { default as BaseDocPresenter } from './components/BaseDocPresenter.svelte'
export { default as DocsNavigator } from './components/DocsNavigator.svelte'
export { default as FixedColumn } from './components/FixedColumn.svelte'
export { default as LinkPresenter } from './components/LinkPresenter.svelte'
export { default as MarkupDiffPresenter } from './components/MarkupDiffPresenter.svelte'
+2
View File
@@ -119,6 +119,7 @@
"@hcengineering/workbench-assets": "^0.6.7",
"@hcengineering/attachment-assets": "^0.6.7",
"@hcengineering/lead-assets": "^0.6.0",
"@hcengineering/drive-assets": "^0.6.0",
"@hcengineering/gmail-assets": "^0.6.0",
"@hcengineering/inventory-assets": "^0.6.7",
"@hcengineering/templates-assets": "^0.6.7",
@@ -144,6 +145,7 @@
"@hcengineering/workbench": "^0.6.9",
"@hcengineering/attachment": "^0.6.9",
"@hcengineering/lead": "^0.6.0",
"@hcengineering/drive": "^0.6.0",
"@hcengineering/gmail": "^0.6.15",
"@hcengineering/inventory": "^0.6.7",
"@hcengineering/templates": "^0.6.7",
+3
View File
@@ -103,6 +103,7 @@ import { calendarId } from '@hcengineering/calendar'
import { chunterId } from '@hcengineering/chunter'
import { contactId } from '@hcengineering/contact'
import { documentId } from '@hcengineering/document'
import { driveId } from '@hcengineering/drive'
import { gmailId } from '@hcengineering/gmail'
import { hrId } from '@hcengineering/hr'
import { inventoryId } from '@hcengineering/inventory'
@@ -134,6 +135,7 @@ import calendarEn from '@hcengineering/calendar-assets/lang/en.json'
import chunterEn from '@hcengineering/chunter-assets/lang/en.json'
import contactEn from '@hcengineering/contact-assets/lang/en.json'
import documentEn from '@hcengineering/document-assets/lang/en.json'
import driveEn from '@hcengineering/drive-assets/lang/en.json'
import gmailEn from '@hcengineering/gmail-assets/lang/en.json'
import hrEn from '@hcengineering/hr-assets/lang/en.json'
import inventoryEn from '@hcengineering/inventory-assets/lang/en.json'
@@ -181,6 +183,7 @@ addStringsLoader(hrId, async (lang: string) => hrEn)
addStringsLoader(bitrixId, async (lang: string) => bitrixEn)
addStringsLoader(requestId, async (lang: string) => requestEn)
addStringsLoader(documentId, async (lang: string) => documentEn)
addStringsLoader(driveId, async (lang: string) => driveEn)
/**
* @public
+35
View File
@@ -1656,6 +1656,41 @@
"packageName": "@hcengineering/model-server-document",
"projectFolder": "models/server-document",
"shouldPublish": false
},
{
"packageName": "@hcengineering/drive",
"projectFolder": "plugins/drive",
"shouldPublish": false
},
{
"packageName": "@hcengineering/drive-assets",
"projectFolder": "plugins/drive-assets",
"shouldPublish": false
},
{
"packageName": "@hcengineering/drive-resources",
"projectFolder": "plugins/drive-resources",
"shouldPublish": false
},
{
"packageName": "@hcengineering/model-drive",
"projectFolder": "models/drive",
"shouldPublish": false
},
{
"packageName": "@hcengineering/server-drive",
"projectFolder": "server-plugins/drive",
"shouldPublish": false
},
{
"packageName": "@hcengineering/model-server-drive",
"projectFolder": "models/server-drive",
"shouldPublish": false
},
{
"packageName": "@hcengineering/server-drive-resources",
"projectFolder": "server-plugins/drive-resources",
"shouldPublish": false
}
]
}
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
@@ -0,0 +1,40 @@
{
"name": "@hcengineering/server-drive-resources",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/drive": "^0.6.0"
}
}
@@ -0,0 +1,79 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
type Class,
type Doc,
type Hierarchy,
type Ref,
type Tx,
type TxRemoveDoc,
TxProcessor,
DocumentQuery,
FindOptions,
FindResult
} from '@hcengineering/core'
import drive, { type File, type Folder } from '@hcengineering/drive'
import type { TriggerControl } from '@hcengineering/server-core'
/**
* @public
*/
export async function OnFileDelete (
tx: Tx,
{ removedMap, ctx, storageAdapter, workspace }: TriggerControl
): Promise<Tx[]> {
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<File>
// Obtain document being deleted.
const attach = removedMap.get(rmTx.objectId) as File
if (attach === undefined) {
return []
}
await storageAdapter.remove(ctx, workspace, [attach.file])
return []
}
/**
* @public
*/
export async function FindFolderResources (
doc: Doc,
hiearachy: Hierarchy,
findAll: <T extends Doc>(
clazz: Ref<Class<T>>,
query: DocumentQuery<T>,
options?: FindOptions<T>
) => Promise<FindResult<T>>
): Promise<Doc[]> {
const folder = doc as Folder
const files = await findAll(drive.class.File, { parent: folder._id })
const folders = await findAll(drive.class.Folder, { parent: folder._id })
return [...files, ...folders]
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnFileDelete
},
function: {
FindFolderResources
}
})
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "@hcengineering/server-drive",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@types/node": "~20.11.16",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/core": "^0.6.28",
"@hcengineering/platform": "^0.6.9",
"@hcengineering/server-core": "^0.6.1"
}
}
+35
View File
@@ -0,0 +1,35 @@
//
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { ObjectDDParticipantFunc, TriggerFunc } from '@hcengineering/server-core'
/**
* @public
*/
export const serverDriveId = 'server-drive' as Plugin
/**
* @public
*/
export default plugin(serverDriveId, {
trigger: {
OnFileDelete: '' as Resource<TriggerFunc>
},
function: {
FindFolderResources: '' as Resource<ObjectDDParticipantFunc>
}
})
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}