diff --git a/client/public/assets/images/download.svg b/client/public/assets/images/download.svg new file mode 100644 index 00000000..74550954 --- /dev/null +++ b/client/public/assets/images/download.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/src/components/atoms/icon/icon.ts b/client/src/components/atoms/icon/icon.ts index 2d4b1d2d..0653d47b 100644 --- a/client/src/components/atoms/icon/icon.ts +++ b/client/src/components/atoms/icon/icon.ts @@ -18,6 +18,7 @@ const icon = [ 'fail', 'add-circle', 'close-circle', + 'download', ] as const const size = ['small', 'medium', 'large', 'xlarge'] as const diff --git a/client/src/components/atoms/toast/toast.ts b/client/src/components/atoms/toast/toast.ts index f3bd17dd..69131ee3 100644 --- a/client/src/components/atoms/toast/toast.ts +++ b/client/src/components/atoms/toast/toast.ts @@ -50,7 +50,7 @@ template.innerHTML = ` }
- +

diff --git a/client/src/components/molecules/canvas-layer/background-layer.ts b/client/src/components/molecules/canvas-layer/background-layer.ts index d61df68b..3fa74f41 100644 --- a/client/src/components/molecules/canvas-layer/background-layer.ts +++ b/client/src/components/molecules/canvas-layer/background-layer.ts @@ -38,6 +38,10 @@ export default class VCanvasBackgroundLayer extends VComponent { return ['draw', 'erase'].includes(this.phase) } + get canvas() { + return this.$root + } + constructor() { super(template) } diff --git a/client/src/components/molecules/canvas-layer/image-layer.ts b/client/src/components/molecules/canvas-layer/image-layer.ts index 3729303f..6647400b 100644 --- a/client/src/components/molecules/canvas-layer/image-layer.ts +++ b/client/src/components/molecules/canvas-layer/image-layer.ts @@ -82,6 +82,10 @@ export default class VCanvasImageLayer extends VComponent { return takeSnapshot(this.$root) } + get canvas() { + return this.$root + } + constructor() { super(template) } diff --git a/client/src/components/molecules/confirm-dialog/confirm-dialog.ts b/client/src/components/molecules/confirm-dialog/confirm-dialog.ts index 24e1fe84..a24e345d 100644 --- a/client/src/components/molecules/confirm-dialog/confirm-dialog.ts +++ b/client/src/components/molecules/confirm-dialog/confirm-dialog.ts @@ -10,7 +10,6 @@ template.innerHTML = ` } - Confirm
μ·¨μ†Œ diff --git a/client/src/components/organisms/canvas-container/canvas-container.ts b/client/src/components/organisms/canvas-container/canvas-container.ts index ccaf2dd3..3181e068 100644 --- a/client/src/components/organisms/canvas-container/canvas-container.ts +++ b/client/src/components/organisms/canvas-container/canvas-container.ts @@ -1,9 +1,11 @@ import { VComponent } from '@/modules/v-component' +import VCanvasBackgroundLayer from '@molecules/canvas-layer/background-layer' import VCanvasImageLayer from '@molecules/canvas-layer/image-layer' import VCanvasDrawingLayer from '@molecules/canvas-layer/drawing-layer' import { getOneTimeSessionId } from '@/services/session' import { addArchive, addOrUpdateArchive } from '@/services/archive' import type { Archive } from '@/services/archive' +import { showToast } from '@/services/toast' import { lastOf } from '@/utils/ramda' import { CanvasMetaContext, @@ -34,6 +36,7 @@ template.innerHTML = ` export default class VCanvasContainer extends VComponent { static tag = 'v-canvas-container' + private backgroundLayer!: VCanvasBackgroundLayer private imageLayer!: VCanvasImageLayer private drawingLayer!: VCanvasDrawingLayer @@ -62,14 +65,18 @@ export default class VCanvasContainer extends VComponent { } private initLayer() { + const backgroundLayer = this.$shadow.querySelector( + 'v-canvas-background-layer' + ) const imageLayer = this.$shadow.querySelector('v-canvas-image-layer') const drawingLayer = this.$shadow.querySelector('v-canvas-drawing-layer') - if (!imageLayer || !drawingLayer) { + if (!backgroundLayer || !imageLayer || !drawingLayer) { console.error('🚨 canvas container need drawing and image layer') return } + this.backgroundLayer = backgroundLayer this.imageLayer = imageLayer this.drawingLayer = drawingLayer } @@ -90,6 +97,7 @@ export default class VCanvasContainer extends VComponent { protected subscribeEventBus() { EventBus.getInstance().on(EVENT_KEY.SAVE_ARCHIVE, this.onSaveArchive.bind(this)) EventBus.getInstance().on(EVENT_KEY.CREATE_NEW_ARCHIVE, this.onCreateNewArchive.bind(this)) + EventBus.getInstance().on(EVENT_KEY.DOWNLOAD, this.onDownload.bind(this)) } private async onSaveArchive() { @@ -129,4 +137,27 @@ export default class VCanvasContainer extends VComponent { ArchiveContext.dispatch({ action: 'FETCH_ARCHIVES_FROM_IDB' }) } + + private onDownload() { + const $origin = this.backgroundLayer.canvas + const $canvas = document.createElement('canvas') + $canvas.width = $origin.width + $canvas.height = $origin.height + + const ctx = $canvas.getContext('2d') + if (!ctx) { + showToast('DOWNLOAD', 'FAIL') + return + } + + ctx.drawImage(this.backgroundLayer.canvas, 0, 0) + ctx.drawImage(this.imageLayer.canvas, 0, 0) + ctx.drawImage(this.drawingLayer.canvas, 0, 0) + + const $link = document.createElement('a') + $link.download = 'image.png' + $link.href = $canvas.toDataURL('image/png') + $link.click() + showToast('DOWNLOAD', 'SUCCESS') + } } diff --git a/client/src/components/organisms/canvas-toolbox/canvas-toolbox.ts b/client/src/components/organisms/canvas-toolbox/canvas-toolbox.ts index 1d378501..31423f7a 100644 --- a/client/src/components/organisms/canvas-toolbox/canvas-toolbox.ts +++ b/client/src/components/organisms/canvas-toolbox/canvas-toolbox.ts @@ -42,6 +42,7 @@ template.innerHTML = ` + @@ -210,6 +211,9 @@ export default class VCanvasToolbox extends VComponent { case 'folder': this.enterFolderPhase() break + case 'download': + this.enterDownloadPhase() + break default: break } @@ -243,6 +247,10 @@ export default class VCanvasToolbox extends VComponent { this.handleOpenArchiveMenu() } + enterDownloadPhase() { + EventBus.getInstance().emit(EVENT_KEY.DOWNLOAD) + } + handleChangePencilColor(ev: Event) { const color = (ev as CustomEvent).detail.value CanvasDrawingContext.dispatch({ action: 'SET_PENCIL_COLOR', data: color }) diff --git a/client/src/event-bus/constant.ts b/client/src/event-bus/constant.ts index c5f3bf50..92808255 100644 --- a/client/src/event-bus/constant.ts +++ b/client/src/event-bus/constant.ts @@ -5,4 +5,5 @@ export const enum EVENT_KEY { 'CLEAR_ALL' = 'CLEAR_ALL', 'SHOW_TOAST' = 'SHOW_TOAST', 'SHOW_CONFIRM' = 'SHOW_CONFIRM', + 'DOWNLOAD' = 'DOWNLOAD', } diff --git a/client/src/services/toast.ts b/client/src/services/toast.ts index 10b27cc4..f04b0ba2 100644 --- a/client/src/services/toast.ts +++ b/client/src/services/toast.ts @@ -4,39 +4,51 @@ const TOAST_MESSAGE = { ADD_ARCHIVE: { SUCCESS: { variant: 'success', - title: '성곡', + title: '', description: 'μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', }, FAIL: { variant: 'fail', - title: 'μ‹€νŒ¨', + title: '', description: 'μž μ‹œ 후에 λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.', }, }, SAVE_ARCHIVE: { SUCCESS: { variant: 'success', - title: '성곡', + title: '', description: 'μ €μž₯λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', }, FAIL: { variant: 'fail', - title: 'μ‹€νŒ¨', + title: '', description: 'μž μ‹œ 후에 λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.', }, }, DELETE_ARCHIVE: { SUCCESS: { variant: 'success', - title: '성곡', + title: '', description: 'μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.', }, FAIL: { variant: 'fail', - title: 'μ‹€νŒ¨', + title: '', description: 'μž μ‹œ 후에 λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.', }, }, + DOWNLOAD: { + SUCCESS: { + variant: 'success', + title: '', + description: 'λ‹€μš΄λ‘œλ“œμ— μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.', + }, + FAIL: { + variant: 'fail', + title: '', + description: 'λ‹€μš΄λ‘œλ“œμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.', + }, + }, } export function showToast(category: keyof typeof TOAST_MESSAGE, type: 'SUCCESS' | 'FAIL') {