diff options
Diffstat (limited to 'packages/frontend/src/components/MkWatermarkEditorDialog.vue')
| -rw-r--r-- | packages/frontend/src/components/MkWatermarkEditorDialog.vue | 162 |
1 files changed, 108 insertions, 54 deletions
diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.vue index 3b3f20d8d1..6cd2111598 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.vue @@ -18,20 +18,21 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.root"> <div :class="$style.container"> - <div :class="$style.preview"> + <div :class="[$style.preview, prefer.s.animation ? $style.animatedBg : null]"> <canvas ref="canvasEl" :class="$style.previewCanvas"></canvas> <div :class="$style.previewContainer"> <div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div> <div v-if="props.image == null" class="_acrylic" :class="$style.previewControls"> <button class="_button" :class="[$style.previewControlsButton, sampleImageType === '3_2' ? $style.active : null]" @click="sampleImageType = '3_2'"><i class="ti ti-crop-landscape"></i></button> <button class="_button" :class="[$style.previewControlsButton, sampleImageType === '2_3' ? $style.active : null]" @click="sampleImageType = '2_3'"><i class="ti ti-crop-portrait"></i></button> + <button class="_button" :class="[$style.previewControlsButton]" @click="choiceImage"><i class="ti ti-upload"></i></button> </div> </div> </div> <div :class="$style.controls"> <div class="_spacer _gaps"> <div class="_gaps_s"> - <MkFolder v-for="(layer, i) in preset.layers" :key="layer.id" :defaultOpen="false" :canPage="false"> + <MkFolder v-for="(layer, i) in layers" :key="layer.id" :defaultOpen="false" :canPage="false"> <template #label> <div v-if="layer.type === 'text'">{{ i18n.ts._watermarkEditor.text }}</div> <div v-if="layer.type === 'image'">{{ i18n.ts._watermarkEditor.image }}</div> @@ -49,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <XLayer - v-model:layer="preset.layers[i]" + v-model:layer="layers[i]" ></XLayer> </MkFolder> @@ -64,8 +65,8 @@ SPDX-License-Identifier: AGPL-3.0-only <script setup lang="ts"> import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick } from 'vue'; -import type { WatermarkPreset } from '@/utility/watermark.js'; -import { WatermarkRenderer } from '@/utility/watermark.js'; +import type { WatermarkLayers, WatermarkPreset } from '@/utility/watermark/WatermarkRenderer.js'; +import { WatermarkRenderer } from '@/utility/watermark/WatermarkRenderer.js'; import { i18n } from '@/i18n.js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import MkSelect from '@/components/MkSelect.vue'; @@ -77,6 +78,7 @@ import { deepClone } from '@/utility/clone.js'; import { ensureSignin } from '@/i.js'; import { genId } from '@/utility/id.js'; import { useMkSelect } from '@/composables/use-mkselect.js'; +import { prefer } from '@/preferences.js'; const $i = ensureSignin(); @@ -161,18 +163,22 @@ function createCheckerLayer(): WatermarkPreset['layers'][number] { } const props = defineProps<{ + presetEditMode?: boolean; preset?: WatermarkPreset | null; + layers?: WatermarkLayers | null; image?: File | null; }>(); -const preset = reactive<WatermarkPreset>(deepClone(props.preset) ?? { +const preset = deepClone(props.preset) ?? { id: genId(), name: '', - layers: [], -}); +}; + +const layers = reactive<WatermarkLayers>(props.layers ?? []); const emit = defineEmits<{ - (ev: 'ok', preset: WatermarkPreset): void; + (ev: 'ok', layers: WatermarkLayers): void; + (ev: 'presetOk', preset: WatermarkPreset): void; (ev: 'cancel'): void; (ev: 'closed'): void; }>(); @@ -180,19 +186,21 @@ const emit = defineEmits<{ const dialog = useTemplateRef('dialog'); async function cancel() { - const { canceled } = await os.confirm({ - type: 'question', - text: i18n.ts._watermarkEditor.quitWithoutSaveConfirm, - }); - if (canceled) return; + if (props.presetEditMode) { + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.ts._watermarkEditor.quitWithoutSaveConfirm, + }); + if (canceled) return; + } emit('cancel'); dialog.value?.close(); } -watch(preset, async (newValue, oldValue) => { +watch(layers, async (newValue, oldValue) => { if (renderer != null) { - renderer.setLayers(preset.layers); + renderer.render(layers); } }, { deep: true }); @@ -212,6 +220,7 @@ const sampleImage_2_3_loading = new Promise<void>(resolve => { const sampleImageType = ref(props.image != null ? 'provided' : '3_2'); watch(sampleImageType, async () => { + if (sampleImageType.value === 'provided') return; if (renderer != null) { renderer.destroy(false); renderer = null; @@ -219,6 +228,20 @@ watch(sampleImageType, async () => { } }); +let imageFile = props.image; + +async function choiceImage() { + const files = await os.chooseFileFromPc({ multiple: false }); + if (files.length === 0) return; + imageFile = files[0]; + sampleImageType.value = 'provided'; + if (renderer != null) { + renderer.destroy(false); + renderer = null; + initRenderer(); + } +} + let renderer: WatermarkRenderer | null = null; let imageBitmap: ImageBitmap | null = null; @@ -239,8 +262,8 @@ async function initRenderer() { renderHeight: 1500, image: sampleImage_2_3, }); - } else if (props.image != null) { - imageBitmap = await window.createImageBitmap(props.image); + } else if (imageFile != null) { + imageBitmap = await window.createImageBitmap(imageFile); const MAX_W = 1000; const MAX_H = 1000; @@ -249,8 +272,8 @@ async function initRenderer() { if (w > MAX_W || h > MAX_H) { const scale = Math.min(MAX_W / w, MAX_H / h); - w *= scale; - h *= scale; + w = Math.floor(w * scale); + h = Math.floor(h * scale); } renderer = new WatermarkRenderer({ @@ -261,9 +284,7 @@ async function initRenderer() { }); } - await renderer!.setLayers(preset.layers); - - renderer!.render(); + await renderer!.render(layers); } onMounted(async () => { @@ -274,7 +295,15 @@ onMounted(async () => { await sampleImage_3_2_loading; await sampleImage_2_3_loading; - await initRenderer(); + try { + await initRenderer(); + } catch (err) { + console.error(err); + os.alert({ + type: 'error', + text: i18n.ts._watermarkEditor.failedToLoadImage, + }); + } closeWaiting(); }); @@ -291,77 +320,93 @@ onUnmounted(() => { }); async function save() { - const { canceled, result: name } = await os.inputText({ - title: i18n.ts.name, - default: preset.name, - }); - if (canceled) return; + if (props.presetEditMode) { + const { canceled, result: name } = await os.inputText({ + title: i18n.ts.name, + default: preset.name, + }); + if (canceled) return; - preset.name = name || ''; + preset.name = name || ''; - dialog.value?.close(); - if (renderer != null) { - renderer.destroy(); - renderer = null; - } + dialog.value?.close(); + if (renderer != null) { + renderer.destroy(); + renderer = null; + } - emit('ok', preset); + emit('presetOk', { + ...preset, + layers: deepClone(layers), + }); + } else { + dialog.value?.close(); + if (renderer != null) { + renderer.destroy(); + renderer = null; + } + + emit('ok', layers); + } } function addLayer(ev: MouseEvent) { os.popupMenu([{ text: i18n.ts._watermarkEditor.text, action: () => { - preset.layers.push(createTextLayer()); + layers.push(createTextLayer()); }, }, { text: i18n.ts._watermarkEditor.image, action: () => { - preset.layers.push(createImageLayer()); + layers.push(createImageLayer()); }, }, { text: i18n.ts._watermarkEditor.qr, action: () => { - preset.layers.push(createQrLayer()); + layers.push(createQrLayer()); }, }, { text: i18n.ts._watermarkEditor.stripe, action: () => { - preset.layers.push(createStripeLayer()); + layers.push(createStripeLayer()); }, }, { text: i18n.ts._watermarkEditor.polkadot, action: () => { - preset.layers.push(createPolkadotLayer()); + layers.push(createPolkadotLayer()); }, }, { text: i18n.ts._watermarkEditor.checker, action: () => { - preset.layers.push(createCheckerLayer()); + layers.push(createCheckerLayer()); }, }], ev.currentTarget ?? ev.target); } function swapUpLayer(layer: WatermarkPreset['layers'][number]) { - const index = preset.layers.findIndex(l => l.id === layer.id); + const index = layers.findIndex(l => l.id === layer.id); if (index > 0) { - const tmp = preset.layers[index - 1]; - preset.layers[index - 1] = preset.layers[index]; - preset.layers[index] = tmp; + const tmp = layers[index - 1]; + layers[index - 1] = layers[index]; + layers[index] = tmp; } } function swapDownLayer(layer: WatermarkPreset['layers'][number]) { - const index = preset.layers.findIndex(l => l.id === layer.id); - if (index < preset.layers.length - 1) { - const tmp = preset.layers[index + 1]; - preset.layers[index + 1] = preset.layers[index]; - preset.layers[index] = tmp; + const index = layers.findIndex(l => l.id === layer.id); + if (index < layers.length - 1) { + const tmp = layers[index + 1]; + layers[index + 1] = layers[index]; + layers[index] = tmp; } } function removeLayer(layer: WatermarkPreset['layers'][number]) { - preset.layers = preset.layers.filter(l => l.id !== layer.id); + const index = layers.findIndex(l => l.id === layer.id); + if (index !== -1) { + layers.splice(index, 1); + } } </script> @@ -380,8 +425,17 @@ function removeLayer(layer: WatermarkPreset['layers'][number]) { .preview { position: relative; background-color: var(--MI_THEME-bg); - background-size: auto auto; - background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px); + background-image: linear-gradient(135deg, transparent 30%, var(--MI_THEME-panel) 30%, var(--MI_THEME-panel) 50%, transparent 50%, transparent 80%, var(--MI_THEME-panel) 80%, var(--MI_THEME-panel) 100%); + background-size: 20px 20px; +} + +.animatedBg { + animation: bg 1.2s linear infinite; +} + +@keyframes bg { + 0% { background-position: 0 0; } + 100% { background-position: -20px -20px; } } .previewContainer { |