From 2bccea9078d555c4a18ee18c06592851aba41171 Mon Sep 17 00:00:00 2001 From: Ilya Sumbatyants Date: Wed, 12 Jan 2022 16:52:22 +0700 Subject: [PATCH] Introduce image cropper (#805) Signed-off-by: Ilya Sumbatyants --- common/config/rush/pnpm-lock.yaml | 79 +++++++++++++- dev/prod/package.json | 4 +- dev/prod/src/platform.ts | 2 + packages/presentation/package.json | 3 +- .../presentation/src/components/Avatar.svelte | 14 ++- .../src/components/EditAvatarPopup.svelte | 88 +++++++++++++++ .../src/components/EditableAvatar.svelte | 58 ++++++++++ packages/presentation/src/index.ts | 1 + packages/presentation/src/utils.ts | 9 ++ .../src/components/Attachments.svelte | 2 +- plugins/attachment-resources/src/index.ts | 4 + plugins/attachment-resources/src/utils.ts | 9 +- plugins/attachment/src/index.ts | 7 +- plugins/contact-resources/package.json | 3 +- .../src/components/CreatePerson.svelte | 21 +++- .../src/components/EditPerson.svelte | 16 ++- plugins/image-cropper-resources/.eslintrc.js | 7 ++ plugins/image-cropper-resources/package.json | 38 +++++++ .../image-cropper-resources/postcss.config.js | 5 + .../src/components/Cropper.svelte | 102 ++++++++++++++++++ plugins/image-cropper-resources/src/index.ts | 25 +++++ .../image-cropper-resources/svelte.config.js | 5 + plugins/image-cropper-resources/tsconfig.json | 15 +++ plugins/image-cropper/.eslintrc.js | 7 ++ plugins/image-cropper/.npmignore | 4 + plugins/image-cropper/config/rig.json | 18 ++++ plugins/image-cropper/package.json | 32 ++++++ plugins/image-cropper/src/index.ts | 29 +++++ plugins/image-cropper/tsconfig.json | 9 ++ .../src/components/CreateCandidate.svelte | 27 +++-- plugins/recruit-resources/src/utils.ts | 43 -------- rush.json | 10 ++ server/front/src/app.ts | 30 +++--- 33 files changed, 646 insertions(+), 80 deletions(-) create mode 100644 packages/presentation/src/components/EditAvatarPopup.svelte create mode 100644 packages/presentation/src/components/EditableAvatar.svelte create mode 100644 plugins/image-cropper-resources/.eslintrc.js create mode 100644 plugins/image-cropper-resources/package.json create mode 100644 plugins/image-cropper-resources/postcss.config.js create mode 100644 plugins/image-cropper-resources/src/components/Cropper.svelte create mode 100644 plugins/image-cropper-resources/src/index.ts create mode 100644 plugins/image-cropper-resources/svelte.config.js create mode 100644 plugins/image-cropper-resources/tsconfig.json create mode 100644 plugins/image-cropper/.eslintrc.js create mode 100644 plugins/image-cropper/.npmignore create mode 100644 plugins/image-cropper/config/rig.json create mode 100644 plugins/image-cropper/package.json create mode 100644 plugins/image-cropper/src/index.ts create mode 100644 plugins/image-cropper/tsconfig.json delete mode 100644 plugins/recruit-resources/src/utils.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 407fb10602..63d60d1f90 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -34,6 +34,8 @@ specifiers: '@rush-temp/gmail': file:./projects/gmail.tgz '@rush-temp/gmail-assets': file:./projects/gmail-assets.tgz '@rush-temp/gmail-resources': file:./projects/gmail-resources.tgz + '@rush-temp/image-cropper': file:./projects/image-cropper.tgz + '@rush-temp/image-cropper-resources': file:./projects/image-cropper-resources.tgz '@rush-temp/lead': file:./projects/lead.tgz '@rush-temp/lead-assets': file:./projects/lead-assets.tgz '@rush-temp/lead-resources': file:./projects/lead-resources.tgz @@ -127,6 +129,7 @@ specifiers: commander: ^8.1.0 compression-webpack-plugin: ~9.0.0 cors: ^2.8.5 + cropperjs: ~1.5.12 cross-env: ^7.0.3 css-loader: ^5.2.1 deep-equal: ^2.0.5 @@ -163,6 +166,7 @@ specifiers: sass: ^1.37.5 sass-loader: ^12.1.0 simplytyped: ^3.3.0 + smartcrop: ~2.0.5 style-loader: ^3.2.1 svelte-check: ^2.2.10 svelte-preprocess: ^4.7.4 @@ -211,6 +215,8 @@ dependencies: '@rush-temp/gmail': file:projects/gmail.tgz '@rush-temp/gmail-assets': file:projects/gmail-assets.tgz '@rush-temp/gmail-resources': file:projects/gmail-resources.tgz_096c09b0b673a57c275d9767a12070b1 + '@rush-temp/image-cropper': file:projects/image-cropper.tgz + '@rush-temp/image-cropper-resources': file:projects/image-cropper-resources.tgz_096c09b0b673a57c275d9767a12070b1 '@rush-temp/lead': file:projects/lead.tgz '@rush-temp/lead-assets': file:projects/lead-assets.tgz '@rush-temp/lead-resources': file:projects/lead-resources.tgz_096c09b0b673a57c275d9767a12070b1 @@ -304,6 +310,7 @@ dependencies: commander: 8.3.0 compression-webpack-plugin: 9.0.1_webpack@5.65.0 cors: 2.8.5 + cropperjs: 1.5.12 cross-env: 7.0.3 css-loader: 5.2.7_webpack@5.65.0 deep-equal: 2.0.5 @@ -340,6 +347,7 @@ dependencies: sass: 1.45.0 sass-loader: 12.4.0_sass@1.45.0+webpack@5.65.0 simplytyped: 3.3.0_typescript@4.5.4 + smartcrop: 2.0.5 style-loader: 3.3.1_webpack@5.65.0 svelte-check: 2.2.11_ac194b5590200ebf8338e0f86ec190f4 svelte-preprocess: 4.10.1_3ae2e5fc7d8fb60bbcea513ad0b15c0f @@ -3530,6 +3538,10 @@ packages: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: false + /cropperjs/1.5.12: + resolution: {integrity: sha512-re7UdjE5UnwdrovyhNzZ6gathI4Rs3KGCBSc8HCIjUo5hO42CtzyblmWLj6QWVw7huHyDMfpKxhiO2II77nhDw==} + dev: false + /cross-env/7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -8877,6 +8889,10 @@ packages: is-fullwidth-code-point: 3.0.0 dev: false + /smartcrop/2.0.5: + resolution: {integrity: sha512-aXoHTM8XlC51g96kgZkYxZ2mx09/ibOrIVLiUNOFozV/MHmFSgEr1/5CKVBoFD5vd+re2wSy0xra21CyjRITzA==} + dev: false + /snapdragon-node/2.1.1: resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} engines: {node: '>=0.10.0'} @@ -10926,7 +10942,7 @@ packages: dev: false file:projects/contact-resources.tgz_096c09b0b673a57c275d9767a12070b1: - resolution: {integrity: sha512-5c2Hkvtnj+3t/r1Z+RFQuoxDwYm6ndTPCxFns6PK3SqCs/KfWREism8tiswJXrB2FNSQkb2CE7yvsftKZFcbPQ==, tarball: file:projects/contact-resources.tgz} + resolution: {integrity: sha512-5m/Rr4eGcqsTpvxL8nYdwl7Vn74QrZXdcgIDGbOJaRWh8uXuQBsDIUgE4wCrL49sgOJQEq/dUIfJVA9ndBOd5A==, tarball: file:projects/contact-resources.tgz} id: file:projects/contact-resources.tgz name: '@rush-temp/contact-resources' version: 0.0.0 @@ -11363,6 +11379,63 @@ packages: - supports-color dev: false + file:projects/image-cropper-resources.tgz_096c09b0b673a57c275d9767a12070b1: + resolution: {integrity: sha512-ljTYKZOYl34hx76ETCFNAcExK+MP6WSS09tbRRixWxD7SYc7NmBeMntGyZTy3qNu4/jAwNaDAIH52kXQg9ktXQ==, tarball: file:projects/image-cropper-resources.tgz} + id: file:projects/image-cropper-resources.tgz + name: '@rush-temp/image-cropper-resources' + version: 0.0.0 + dependencies: + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + cropperjs: 1.5.12 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.44.3 + prettier: 2.5.1 + prettier-plugin-svelte: 2.5.1_prettier@2.5.1+svelte@3.44.3 + sass: 1.45.0 + smartcrop: 2.0.5 + svelte: 3.44.3 + svelte-check: 2.2.11_4374c622c67ed7479ff0e44c29d09bce + svelte-loader: 3.1.2_svelte@3.44.3 + svelte-preprocess: 4.10.1_14d64cad431e31f100de7363af24a44f + typescript: 4.5.4 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - node-sass + - postcss + - postcss-load-config + - pug + - stylus + - sugarss + - supports-color + dev: false + + file:projects/image-cropper.tgz: + resolution: {integrity: sha512-7Fj5//tMmR0uKUwcnFrk1KHmI+N+CLNmOiNPApLUtdK7q6clzFT8LMTDZapnbh93NdTCVXa+DxVLwzNGaeqsQA==, tarball: file:projects/image-cropper.tgz} + name: '@rush-temp/image-cropper' + version: 0.0.0 + dependencies: + '@rushstack/heft': 0.41.8 + '@types/heft-jest': 1.0.2 + '@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237 + '@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4 + eslint: 7.32.0 + eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a + eslint-plugin-import: 2.25.3_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.2.0_eslint@7.32.0 + prettier: 2.5.1 + typescript: 4.5.4 + transitivePeerDependencies: + - supports-color + dev: false + file:projects/lead-assets.tgz: resolution: {integrity: sha512-cRYB8PutP6HmaJjoEMLIEyMQEhKAQaCu0w2NJMF5TUW9vokia/22TXsHo1+xEGI1rx2epywbGXet/fL40tdbDw==, tarball: file:projects/lead-assets.tgz} name: '@rush-temp/lead-assets' @@ -12045,7 +12118,7 @@ packages: dev: false file:projects/presentation.tgz_096c09b0b673a57c275d9767a12070b1: - resolution: {integrity: sha512-tpa5gk8H/quPYXkpBhp5jS0bp/E+Jk7mTGhfE54q4RTr5LOYGTCg2HKmxZWqRV1N3g1Jk7nKAZHWMxMSGHCuKw==, tarball: file:projects/presentation.tgz} + resolution: {integrity: sha512-QhfoHLyegkl3JsleXaZkSWsVQgikJM9jdqkla2mRW3kHyQAYWPBITUnVVWu1qBADViJupNZdYmYEcm7oeMF5og==, tarball: file:projects/presentation.tgz} id: file:projects/presentation.tgz name: '@rush-temp/presentation' version: 0.0.0 @@ -12081,7 +12154,7 @@ packages: dev: false file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4: - resolution: {integrity: sha512-XgKxpfDD6oNTIijCj64CrOXM6sYgZhDuDoy5wWIrr/4Y5QFn7TWpOnSjIgmIXMtLUCGk4L55CAZBK+okRGX25Q==, tarball: file:projects/prod.tgz} + resolution: {integrity: sha512-7OeW4OKQlYw/FrF1rSmbwacpTXSVzNnIPCb/mIP/Q4Kc+uxroItvCRRYUqVH/V9u8W7nyTE7CjDM+KrADL/BAg==, tarball: file:projects/prod.tgz} id: file:projects/prod.tgz name: '@rush-temp/prod' version: 0.0.0 diff --git a/dev/prod/package.json b/dev/prod/package.json index 7d23dd5ea3..4d2068e871 100644 --- a/dev/prod/package.json +++ b/dev/prod/package.json @@ -88,6 +88,8 @@ "@anticrm/lead-resources": "~0.6.0", "@anticrm/gmail": "~0.6.0", "@anticrm/gmail-assets": "~0.6.0", - "@anticrm/gmail-resources": "~0.6.0" + "@anticrm/gmail-resources": "~0.6.0", + "@anticrm/image-cropper": "~0.6.0", + "@anticrm/image-cropper-resources": "~0.6.0" } } diff --git a/dev/prod/src/platform.ts b/dev/prod/src/platform.ts index 812de966af..4fd7bba820 100644 --- a/dev/prod/src/platform.ts +++ b/dev/prod/src/platform.ts @@ -29,6 +29,7 @@ import { attachmentId } from '@anticrm/attachment' import { leadId } from '@anticrm/lead' import { clientId } from '@anticrm/client' import { gmailId } from '@anticrm/gmail' +import { imageCropperId } from '@anticrm/image-cropper' import '@anticrm/login-assets' import '@anticrm/task-assets' @@ -71,4 +72,5 @@ export async function configurePlatform() { addLocation(telegramId, () => import(/* webpackChunkName: "telegram" */ '@anticrm/telegram-resources')) addLocation(attachmentId, () => import(/* webpackChunkName: "attachment" */ '@anticrm/attachment-resources')) addLocation(gmailId, () => import(/* webpackChunkName: "gmail" */ '@anticrm/gmail-resources')) + addLocation(imageCropperId, () => import(/* webpackChunkName: "image-cropper" */ '@anticrm/image-cropper-resources')) } diff --git a/packages/presentation/package.json b/packages/presentation/package.json index 200256cc5d..c236163542 100644 --- a/packages/presentation/package.json +++ b/packages/presentation/package.json @@ -37,6 +37,7 @@ "@anticrm/view": "~0.6.0", "svelte": "^3.37.0", "@anticrm/contact": "~0.6.2", - "@anticrm/login": "~0.6.1" + "@anticrm/login": "~0.6.1", + "@anticrm/image-cropper": "~0.6.0" } } diff --git a/packages/presentation/src/components/Avatar.svelte b/packages/presentation/src/components/Avatar.svelte index 4209ef5a2c..5b2a42427c 100644 --- a/packages/presentation/src/components/Avatar.svelte +++ b/packages/presentation/src/components/Avatar.svelte @@ -16,12 +16,22 @@
diff --git a/packages/presentation/src/components/EditAvatarPopup.svelte b/packages/presentation/src/components/EditAvatarPopup.svelte new file mode 100644 index 0000000000..127464da22 --- /dev/null +++ b/packages/presentation/src/components/EditAvatarPopup.svelte @@ -0,0 +1,88 @@ + + + +
{ dispatch('close') }} /> +
+ {#await CropperP then Cropper} +
+ +
+ + {/await} +
+ + diff --git a/packages/presentation/src/components/EditableAvatar.svelte b/packages/presentation/src/components/EditableAvatar.svelte new file mode 100644 index 0000000000..79a2d4d19a --- /dev/null +++ b/packages/presentation/src/components/EditableAvatar.svelte @@ -0,0 +1,58 @@ + + + + +
+ + +
diff --git a/packages/presentation/src/index.ts b/packages/presentation/src/index.ts index 338db59d99..20b0f02de0 100644 --- a/packages/presentation/src/index.ts +++ b/packages/presentation/src/index.ts @@ -23,6 +23,7 @@ export * from './attributes' export { default as UserBox } from './components/UserBox.svelte' export { default as UserInfo } from './components/UserInfo.svelte' export { default as Avatar } from './components/Avatar.svelte' +export { default as EditableAvatar } from './components/EditableAvatar.svelte' export { default as MessageViewer } from './components/MessageViewer.svelte' export { default as AttributesBar } from './components/AttributesBar.svelte' export { default as AttributeBarEditor } from './components/AttributeBarEditor.svelte' diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index db1a8d6082..a9b8e5d2df 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -114,6 +114,15 @@ export function getFileUrl (file: string): string { return url } +export async function getBlobURL (blob: Blob): Promise { + return await new Promise((resolve) => { + const reader = new FileReader() + + reader.addEventListener('load', () => resolve(reader.result as string), false) + reader.readAsDataURL(blob) + }) +} + /** * @public */ diff --git a/plugins/attachment-resources/src/components/Attachments.svelte b/plugins/attachment-resources/src/components/Attachments.svelte index 98c4268a7a..4d9e91a375 100644 --- a/plugins/attachment-resources/src/components/Attachments.svelte +++ b/plugins/attachment-resources/src/components/Attachments.svelte @@ -38,7 +38,7 @@ async function createAttachment (file: File) { loading++ try { - const uuid = await uploadFile(space, file, objectId) + const uuid = await uploadFile(file, space, objectId) console.log('uploaded file uuid', uuid) client.addCollection(attachment.class.Attachment, space, objectId, _class, 'attachments', { name: file.name, diff --git a/plugins/attachment-resources/src/index.ts b/plugins/attachment-resources/src/index.ts index cb140ad7d4..86ddc95acd 100644 --- a/plugins/attachment-resources/src/index.ts +++ b/plugins/attachment-resources/src/index.ts @@ -17,6 +17,7 @@ import AttachmentsPresenter from './components/AttachmentsPresenter.svelte' import AttachmentPresenter from './components/AttachmentPresenter.svelte' import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte' import Attachments from './components/Attachments.svelte' +import { uploadFile } from './utils' export { Attachments, AttachmentsPresenter } @@ -28,5 +29,8 @@ export default async () => ({ }, activity: { TxAttachmentCreate + }, + helper: { + UploadFile: uploadFile } }) diff --git a/plugins/attachment-resources/src/utils.ts b/plugins/attachment-resources/src/utils.ts index f61a3d32d9..787fb3eddc 100644 --- a/plugins/attachment-resources/src/utils.ts +++ b/plugins/attachment-resources/src/utils.ts @@ -18,14 +18,19 @@ import type { Doc, Ref, Space } from '@anticrm/core' import login from '@anticrm/login' import { getMetadata } from '@anticrm/platform' -export async function uploadFile (space: Ref, file: File, attachedTo: Ref): Promise { +export async function uploadFile (file: File, space?: Ref, attachedTo?: Ref): Promise { console.log(file) const uploadUrl = getMetadata(login.metadata.UploadUrl) const data = new FormData() data.append('file', file) - const url = `${uploadUrl as string}?space=${space}&name=${encodeURIComponent(file.name)}&attachedTo=${attachedTo}` + const params = [['space', space], ['attachedTo', attachedTo]] + .filter((x): x is [string, Ref] => x[1] !== undefined) + .map(([name, value]) => `${name}=${value}`) + .join('&') + + const url = `${uploadUrl as string}?name=${encodeURIComponent(file.name)}&${params}` const resp = await fetch(url, { method: 'POST', headers: { diff --git a/plugins/attachment/src/index.ts b/plugins/attachment/src/index.ts index 3e41f1953d..677790c096 100644 --- a/plugins/attachment/src/index.ts +++ b/plugins/attachment/src/index.ts @@ -14,8 +14,8 @@ // limitations under the License. // -import type { Ref, Class, AttachedDoc } from '@anticrm/core' -import { plugin } from '@anticrm/platform' +import type { Ref, Class, AttachedDoc, Space, Doc } from '@anticrm/core' +import { plugin, Resource } from '@anticrm/platform' import type { Asset, Plugin } from '@anticrm/platform' import { AnyComponent } from '@anticrm/ui' @@ -44,5 +44,8 @@ export default plugin(attachmentId, { }, class: { Attachment: '' as Ref> + }, + helper: { + UploadFile: '' as Resource<(file: File, space?: Ref, attachedTo?: Ref) => Promise> } }) diff --git a/plugins/contact-resources/package.json b/plugins/contact-resources/package.json index c2c426aeaf..963e6a49ba 100644 --- a/plugins/contact-resources/package.json +++ b/plugins/contact-resources/package.json @@ -41,6 +41,7 @@ "@anticrm/view": "~0.6.0", "@anticrm/attachment-resources": "~0.6.0", "@anticrm/panel": "~0.6.0", - "@anticrm/view-resources": "~0.6.0" + "@anticrm/view-resources": "~0.6.0", + "@anticrm/attachment": "~0.6.1" } } diff --git a/plugins/contact-resources/src/components/CreatePerson.svelte b/plugins/contact-resources/src/components/CreatePerson.svelte index 7096f5c715..adcc808b08 100644 --- a/plugins/contact-resources/src/components/CreatePerson.svelte +++ b/plugins/contact-resources/src/components/CreatePerson.svelte @@ -16,9 +16,11 @@ {#if object !== undefined}
- +
diff --git a/plugins/image-cropper-resources/.eslintrc.js b/plugins/image-cropper-resources/.eslintrc.js new file mode 100644 index 0000000000..a327214967 --- /dev/null +++ b/plugins/image-cropper-resources/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'], + parserOptions: { tsconfigRootDir: __dirname }, + settings: { + 'svelte3/ignore-styles': () => true + } +} diff --git a/plugins/image-cropper-resources/package.json b/plugins/image-cropper-resources/package.json new file mode 100644 index 0000000000..622e8d46ca --- /dev/null +++ b/plugins/image-cropper-resources/package.json @@ -0,0 +1,38 @@ +{ + "name": "@anticrm/image-cropper-resources", + "version": "0.6.0", + "main": "src/index.ts", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "echo 'no build for ui'", + "build:docs": "api-extractor run --local", + "lint": "svelte-check && eslint", + "lint:fix": "eslint --fix src", + "format": "prettier --write --plugin-search-dir=. src && eslint --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig": "~0.6.0", + "svelte-loader": "^3.1.2", + "sass": "^1.37.5", + "svelte-preprocess": "^4.7.4", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint": "^7.32.0", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "eslint-plugin-svelte3": "~3.2.1", + "prettier-plugin-svelte": "^2.2.0", + "prettier": "^2.4.1", + "svelte-check": "^2.2.10", + "typescript": "^4.3.5" + }, + "dependencies": { + "svelte": "^3.37.0", + "@anticrm/platform": "~0.6.5", + "cropperjs": "~1.5.12", + "smartcrop": "~2.0.5" + } +} diff --git a/plugins/image-cropper-resources/postcss.config.js b/plugins/image-cropper-resources/postcss.config.js new file mode 100644 index 0000000000..88752c6cb0 --- /dev/null +++ b/plugins/image-cropper-resources/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('autoprefixer') + ] +} diff --git a/plugins/image-cropper-resources/src/components/Cropper.svelte b/plugins/image-cropper-resources/src/components/Cropper.svelte new file mode 100644 index 0000000000..2593fd3964 --- /dev/null +++ b/plugins/image-cropper-resources/src/components/Cropper.svelte @@ -0,0 +1,102 @@ + + + +
+ img + {#await init()} + Waiting... + {/await} +
+ + \ No newline at end of file diff --git a/plugins/image-cropper-resources/src/index.ts b/plugins/image-cropper-resources/src/index.ts new file mode 100644 index 0000000000..e995c43b7a --- /dev/null +++ b/plugins/image-cropper-resources/src/index.ts @@ -0,0 +1,25 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// Copyright © 2021 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 { Resources } from '@anticrm/platform' + +import Cropper from './components/Cropper.svelte' + +export default async (): Promise => ({ + component: { + Cropper + } +}) diff --git a/plugins/image-cropper-resources/svelte.config.js b/plugins/image-cropper-resources/svelte.config.js new file mode 100644 index 0000000000..944a06f73e --- /dev/null +++ b/plugins/image-cropper-resources/svelte.config.js @@ -0,0 +1,5 @@ +const sveltePreprocess = require('svelte-preprocess') + +module.exports = { + preprocess: sveltePreprocess() +}; \ No newline at end of file diff --git a/plugins/image-cropper-resources/tsconfig.json b/plugins/image-cropper-resources/tsconfig.json new file mode 100644 index 0000000000..cabe5aefad --- /dev/null +++ b/plugins/image-cropper-resources/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "target": "esnext", + "module": "esnext", + "declaration": true, + "outDir": "./lib", + "strict": true, + "esModuleInterop": true, + "lib": [ + "esnext", + "dom" + ] + } +} \ No newline at end of file diff --git a/plugins/image-cropper/.eslintrc.js b/plugins/image-cropper/.eslintrc.js new file mode 100644 index 0000000000..5da5872d4a --- /dev/null +++ b/plugins/image-cropper/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.json' + } +} diff --git a/plugins/image-cropper/.npmignore b/plugins/image-cropper/.npmignore new file mode 100644 index 0000000000..e3ec093c38 --- /dev/null +++ b/plugins/image-cropper/.npmignore @@ -0,0 +1,4 @@ +* +!/lib/** +!CHANGELOG.md +/lib/**/__tests__/ diff --git a/plugins/image-cropper/config/rig.json b/plugins/image-cropper/config/rig.json new file mode 100644 index 0000000000..af1257a896 --- /dev/null +++ b/plugins/image-cropper/config/rig.json @@ -0,0 +1,18 @@ +// The "rig.json" file directs tools to look for their config files in an external package. +// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + + /** + * (Required) The name of the rig package to inherit from. + * It should be an NPM package name with the "-rig" suffix. + */ + "rigPackageName": "@anticrm/platform-rig" + + /** + * (Optional) Selects a config profile from the rig package. The name must consist of + * lowercase alphanumeric words separated by hyphens, for example "sample-profile". + * If omitted, then the "default" profile will be used." + */ + // "rigProfile": "your-profile-name" +} diff --git a/plugins/image-cropper/package.json b/plugins/image-cropper/package.json new file mode 100644 index 0000000000..bc4b6c55b2 --- /dev/null +++ b/plugins/image-cropper/package.json @@ -0,0 +1,32 @@ +{ + "name": "@anticrm/image-cropper", + "version": "0.6.0", + "main": "lib/index.js", + "author": "Anticrm Platform Contributors", + "license": "EPL-2.0", + "scripts": { + "build": "heft build", + "build:watch": "tsc", + "lint:fix": "eslint --fix src", + "lint": "eslint src", + "format": "prettier --write src && eslint --fix src" + }, + "devDependencies": { + "@anticrm/platform-rig": "~0.6.0", + "@types/heft-jest": "^1.0.2", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-promise": "^5.1.1", + "eslint-plugin-node": "^11.1.0", + "eslint": "^7.32.0", + "@typescript-eslint/parser": "^5.4.0", + "eslint-config-standard-with-typescript": "^21.0.1", + "prettier": "^2.4.1", + "@rushstack/heft": "^0.41.1", + "typescript": "^4.3.5" + }, + "dependencies": { + "@anticrm/platform": "~0.6.5", + "@anticrm/ui": "~0.6.0" + } +} diff --git a/plugins/image-cropper/src/index.ts b/plugins/image-cropper/src/index.ts new file mode 100644 index 0000000000..b70c2167ea --- /dev/null +++ b/plugins/image-cropper/src/index.ts @@ -0,0 +1,29 @@ +// +// Copyright © 2020, 2021 Anticrm Platform Contributors. +// +// 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 { plugin } from '@anticrm/platform' +import type { Plugin } from '@anticrm/platform' +import type { AnyComponent } from '@anticrm/ui' + +/** + * @public + */ +export const imageCropperId = 'image-cropper' as Plugin + +export default plugin(imageCropperId, { + component: { + Cropper: '' as AnyComponent + } +}) diff --git a/plugins/image-cropper/tsconfig.json b/plugins/image-cropper/tsconfig.json new file mode 100644 index 0000000000..32045300ce --- /dev/null +++ b/plugins/image-cropper/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "lib": ["esnext", "dom"] + } +} \ No newline at end of file diff --git a/plugins/recruit-resources/src/components/CreateCandidate.svelte b/plugins/recruit-resources/src/components/CreateCandidate.svelte index 3f91478468..3dac469a8a 100644 --- a/plugins/recruit-resources/src/components/CreateCandidate.svelte +++ b/plugins/recruit-resources/src/components/CreateCandidate.svelte @@ -18,13 +18,12 @@ import contact, { combineName, Person } from '@anticrm/contact' import type { Data, MixinData, Ref } from '@anticrm/core' import { generateId } from '@anticrm/core' - import { setPlatformStatus, unknownError } from '@anticrm/platform' - import { Avatar, Card, Channels, getClient, PDFViewer } from '@anticrm/presentation' + import { getResource, setPlatformStatus, unknownError } from '@anticrm/platform' + import { EditableAvatar, Card, Channels, getClient, PDFViewer } from '@anticrm/presentation' import type { Candidate } from '@anticrm/recruit' import { CircleButton, EditBox, IconAdd, IconFile as FileIcon, Label, Link, showPopup, Spinner } from '@anticrm/ui' import { createEventDispatcher } from 'svelte' import recruit from '../plugin' - import { uploadFile } from '../utils' import FileUpload from './icons/FileUpload.svelte' import YesNo from './YesNo.svelte' @@ -50,10 +49,15 @@ const candidateId = generateId() async function createCandidate () { + const uploadFile = await getResource(attachment.helper.UploadFile) + const avatarUUID = avatar !== undefined + ? await uploadFile(avatar) + : undefined const candidate: Data = { name: combineName(firstName, lastName), city: object.city, - channels: object.channels + channels: object.channels, + avatar: avatarUUID } const candidateData: MixinData = { title: object.title, @@ -86,7 +90,9 @@ async function createAttachment (file: File) { loading = true try { - resume.uuid = await uploadFile(space, file, candidateId) + const uploadFile = await getResource(attachment.helper.UploadFile) + + resume.uuid = await uploadFile(file, space, candidateId) resume.name = file.name resume.size = file.size resume.type = file.type @@ -111,6 +117,15 @@ const file = inputFile.files?.[0] if (file !== undefined) { createAttachment(file) } } + + let avatar: File | undefined + + function onAvatarDone (e: any) { + const { file } = e.detail + + avatar = file + } + @@ -124,7 +139,7 @@
- +
diff --git a/plugins/recruit-resources/src/utils.ts b/plugins/recruit-resources/src/utils.ts deleted file mode 100644 index 02b83892dc..0000000000 --- a/plugins/recruit-resources/src/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright © 2020, 2021 Anticrm Platform Contributors. -// Copyright © 2021 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 { Ref, Doc, Space } from '@anticrm/core' -import { getMetadata, PlatformError } from '@anticrm/platform' - -import login from '@anticrm/login' - -export async function uploadFile(space: Ref, file: File, attachedTo: Ref): Promise { - console.log(file) - const uploadUrl = getMetadata(login.metadata.UploadUrl) - - const data = new FormData() - data.append('file', file) - - const url = `${uploadUrl}?space=${space}&name=${encodeURIComponent(file.name)}&attachedTo=${attachedTo}` - const resp = await fetch(url, { - method: 'POST', - headers: { - 'Authorization': 'Bearer ' + getMetadata(login.metadata.LoginToken) - }, - body: data - }) - if (resp.status !== 200) { - throw new Error('Can\'t upload file.') - } - const uuid = await resp.text() - console.log(uuid) - return uuid -} diff --git a/rush.json b/rush.json index f06c3451c5..4857fdb943 100644 --- a/rush.json +++ b/rush.json @@ -691,6 +691,16 @@ "projectFolder": "models/demo", "shouldPublish": true }, + { + "packageName": "@anticrm/image-cropper", + "projectFolder": "plugins/image-cropper", + "shouldPublish": true + }, + { + "packageName": "@anticrm/image-cropper-resources", + "projectFolder": "plugins/image-cropper-resources", + "shouldPublish": true + }, { "packageName": "@anticrm/dev-server-chunter-resources", "projectFolder": "dev/server-chunter-resources", diff --git a/server/front/src/app.ts b/server/front/src/app.ts index 404a039b08..4bfb4dbf35 100644 --- a/server/front/src/app.ts +++ b/server/front/src/app.ts @@ -159,9 +159,9 @@ export function start (config: { transactorEndpoint: string, elasticUrl: string, const uuid = await minioUpload(config.minio, payload.workspace, file) console.log('uploaded uuid', uuid) - const name = req.query.name as string - const space = req.query.space as Ref - const attachedTo = req.query.attachedTo as Ref + const name = req.query.name as string | undefined + const space = req.query.space as Ref | undefined + const attachedTo = req.query.attachedTo as Ref | undefined // const name = req.query.name as string // await createAttachment( @@ -175,20 +175,22 @@ export function start (config: { transactorEndpoint: string, elasticUrl: string, // fileId // ) - const elastic = await createElasticAdapter(config.elasticUrl, payload.workspace) + if (name !== undefined && space !== undefined && attachedTo !== undefined) { + const elastic = await createElasticAdapter(config.elasticUrl, payload.workspace) - const indexedDoc: IndexedDoc = { - id: generateId() + '/attachments/' + name as Ref, - _class: attachment.class.Attachment, - space, - modifiedOn: Date.now(), - modifiedBy: 'core:account:System' as Ref, - attachedTo, - data: file.data.toString('base64') + const indexedDoc: IndexedDoc = { + id: generateId() + '/attachments/' + name as Ref, + _class: attachment.class.Attachment, + space, + modifiedOn: Date.now(), + modifiedBy: 'core:account:System' as Ref, + attachedTo, + data: file.data.toString('base64') + } + + await elastic.index(indexedDoc) } - await elastic.index(indexedDoc) - res.status(200).send(uuid) } catch (error) { console.log(error)