diff options
Diffstat (limited to 'packages/frontend/src/components')
4 files changed, 114 insertions, 50 deletions
diff --git a/packages/frontend/src/components/MkPolkadots.vue b/packages/frontend/src/components/MkPolkadots.vue index 285c4d0b79..4f1346b685 100644 --- a/packages/frontend/src/components/MkPolkadots.vue +++ b/packages/frontend/src/components/MkPolkadots.vue @@ -4,14 +4,18 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div :class="[$style.root, accented ? $style.accented : null]"></div> +<div :class="[$style.root, accented ? $style.accented : null, revered ? $style.revered : null]"/> </template> <script lang="ts" setup> const props = withDefaults(defineProps<{ accented?: boolean; + revered?: boolean; + height?: number; }>(), { accented: false, + revered: false, + height: 200, }); </script> @@ -27,14 +31,17 @@ const props = withDefaults(defineProps<{ --dot-size: 2px; --gap-size: 40px; --offset: calc(var(--gap-size) / 2); + --height: v-bind('props.height + "px"'); - height: 200px; - margin-bottom: -200px; - + height: var(--height); background-image: linear-gradient(transparent 60%, transparent 100%), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size)), radial-gradient(var(--c) var(--dot-size), transparent var(--dot-size)); background-position: 0 0, 0 0, var(--offset) var(--offset); background-size: 100% 100%, var(--gap-size) var(--gap-size), var(--gap-size) var(--gap-size); mask-image: linear-gradient(to bottom, black 0%, transparent 100%); pointer-events: none; + + &.revered { + mask-image: linear-gradient(to top, black 0%, transparent 100%); + } } </style> diff --git a/packages/frontend/src/components/MkPositionSelector.vue b/packages/frontend/src/components/MkPositionSelector.vue index 739f55125b..6f12aada30 100644 --- a/packages/frontend/src/components/MkPositionSelector.vue +++ b/packages/frontend/src/components/MkPositionSelector.vue @@ -6,15 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="[$style.root]"> <div :class="$style.items"> - <button class="_button" :class="[$style.item, x === 'left' && y === 'top' ? $style.active : null]" @click="() => { x = 'left'; y = 'top'; }"><i class="ti ti-align-box-left-top"></i></button> - <button class="_button" :class="[$style.item, x === 'center' && y === 'top' ? $style.active : null]" @click="() => { x = 'center'; y = 'top'; }"><i class="ti ti-align-box-center-top"></i></button> - <button class="_button" :class="[$style.item, x === 'right' && y === 'top' ? $style.active : null]" @click="() => { x = 'right'; y = 'top'; }"><i class="ti ti-align-box-right-top"></i></button> - <button class="_button" :class="[$style.item, x === 'left' && y === 'center' ? $style.active : null]" @click="() => { x = 'left'; y = 'center'; }"><i class="ti ti-align-box-left-middle"></i></button> - <button class="_button" :class="[$style.item, x === 'center' && y === 'center' ? $style.active : null]" @click="() => { x = 'center'; y = 'center'; }"><i class="ti ti-align-box-center-middle"></i></button> - <button class="_button" :class="[$style.item, x === 'right' && y === 'center' ? $style.active : null]" @click="() => { x = 'right'; y = 'center'; }"><i class="ti ti-align-box-right-middle"></i></button> - <button class="_button" :class="[$style.item, x === 'left' && y === 'bottom' ? $style.active : null]" @click="() => { x = 'left'; y = 'bottom'; }"><i class="ti ti-align-box-left-bottom"></i></button> - <button class="_button" :class="[$style.item, x === 'center' && y === 'bottom' ? $style.active : null]" @click="() => { x = 'center'; y = 'bottom'; }"><i class="ti ti-align-box-center-bottom"></i></button> - <button class="_button" :class="[$style.item, x === 'right' && y === 'bottom' ? $style.active : null]" @click="() => { x = 'right'; y = 'bottom'; }"><i class="ti ti-align-box-right-bottom"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'left' && y === 'top' ? $style.active : null]" @click="() => { x = 'left'; y = 'top'; }"><i class="ti ti-arrow-up-left"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'center' && y === 'top' ? $style.active : null]" @click="() => { x = 'center'; y = 'top'; }"><i class="ti ti-arrow-up"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'right' && y === 'top' ? $style.active : null]" @click="() => { x = 'right'; y = 'top'; }"><i class="ti ti-arrow-up-right"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'left' && y === 'center' ? $style.active : null]" @click="() => { x = 'left'; y = 'center'; }"><i class="ti ti-arrow-left"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'center' && y === 'center' ? $style.active : null]" @click="() => { x = 'center'; y = 'center'; }"><i class="ti ti-focus-2"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'right' && y === 'center' ? $style.active : null]" @click="() => { x = 'right'; y = 'center'; }"><i class="ti ti-arrow-right"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'left' && y === 'bottom' ? $style.active : null]" @click="() => { x = 'left'; y = 'bottom'; }"><i class="ti ti-arrow-down-left"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'center' && y === 'bottom' ? $style.active : null]" @click="() => { x = 'center'; y = 'bottom'; }"><i class="ti ti-arrow-down"></i></button> + <button v-panel class="_button" :class="[$style.item, x === 'right' && y === 'bottom' ? $style.active : null]" @click="() => { x = 'right'; y = 'bottom'; }"><i class="ti ti-arrow-down-right"></i></button> </div> </div> </template> diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue index 11ae091d90..288293db3f 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.Layer.vue @@ -19,6 +19,18 @@ SPDX-License-Identifier: AGPL-3.0-only </FormSlot> <MkRange + :modelValue="layer.align.margin ?? 0" + :min="0" + :max="0.25" + :step="0.01" + :textConverter="(v) => (v * 100).toFixed(1) + '%'" + continuousUpdate + @update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'text' }>).align.margin = v" + > + <template #label>{{ i18n.ts._watermarkEditor.margin }}</template> + </MkRange> + + <MkRange v-model="layer.scale" :min="0" :max="1" @@ -67,6 +79,18 @@ SPDX-License-Identifier: AGPL-3.0-only </FormSlot> <MkRange + :modelValue="layer.align.margin ?? 0" + :min="0" + :max="0.25" + :step="0.01" + :textConverter="(v) => (v * 100).toFixed(1) + '%'" + continuousUpdate + @update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'image' }>).align.margin = v" + > + <template #label>{{ i18n.ts._watermarkEditor.margin }}</template> + </MkRange> + + <MkRange v-model="layer.scale" :min="0" :max="1" @@ -107,6 +131,55 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> </template> + <template v-else-if="layer.type === 'qr'"> + <MkInput v-model="layer.data" debounce> + <template #label>{{ i18n.ts._watermarkEditor.text }}</template> + <template #caption>{{ i18n.ts._watermarkEditor.leaveBlankToAccountUrl }}</template> + </MkInput> + + <FormSlot> + <template #label>{{ i18n.ts._watermarkEditor.position }}</template> + <MkPositionSelector + v-model:x="layer.align.x" + v-model:y="layer.align.y" + ></MkPositionSelector> + </FormSlot> + + <MkRange + :modelValue="layer.align.margin ?? 0" + :min="0" + :max="0.25" + :step="0.01" + :textConverter="(v) => (v * 100).toFixed(1) + '%'" + continuousUpdate + @update:modelValue="(v) => (layer as Extract<WatermarkPreset['layers'][number], { type: 'qr' }>).align.margin = v" + > + <template #label>{{ i18n.ts._watermarkEditor.margin }}</template> + </MkRange> + + <MkRange + v-model="layer.scale" + :min="0" + :max="1" + :step="0.01" + :textConverter="(v) => (v * 100).toFixed(1) + '%'" + continuousUpdate + > + <template #label>{{ i18n.ts._watermarkEditor.scale }}</template> + </MkRange> + + <MkRange + v-model="layer.opacity" + :min="0" + :max="1" + :step="0.01" + :textConverter="(v) => (v * 100).toFixed(1) + '%'" + continuousUpdate + > + <template #label>{{ i18n.ts._watermarkEditor.opacity }}</template> + </MkRange> + </template> + <template v-else-if="layer.type === 'stripe'"> <MkRange v-model="layer.frequency" diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.vue index 75a45548fd..0d0488d9bc 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.vue @@ -30,22 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div :class="$style.controls"> <div class="_spacer _gaps"> - <MkSelect v-model="type" :items="typeDef"> - <template #label>{{ i18n.ts._watermarkEditor.type }}</template> - </MkSelect> - - <div v-if="type === 'text' || type === 'image'"> - <XLayer - v-for="(layer, i) in preset.layers" - :key="layer.id" - v-model:layer="preset.layers[i]" - ></XLayer> - </div> - <div v-else-if="type === 'advanced'" class="_gaps_s"> + <div class="_gaps_s"> <MkFolder v-for="(layer, i) in preset.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> + <div v-if="layer.type === 'qr'">{{ i18n.ts._watermarkEditor.qr }}</div> <div v-if="layer.type === 'stripe'">{{ i18n.ts._watermarkEditor.stripe }}</div> <div v-if="layer.type === 'polkadot'">{{ i18n.ts._watermarkEditor.polkadot }}</div> <div v-if="layer.type === 'checker'">{{ i18n.ts._watermarkEditor.checker }}</div> @@ -95,7 +85,7 @@ function createTextLayer(): WatermarkPreset['layers'][number] { id: genId(), type: 'text', text: `(c) @${$i.username}`, - align: { x: 'right', y: 'bottom' }, + align: { x: 'right', y: 'bottom', margin: 0 }, scale: 0.3, angle: 0, opacity: 0.75, @@ -109,7 +99,7 @@ function createImageLayer(): WatermarkPreset['layers'][number] { type: 'image', imageId: null, imageUrl: null, - align: { x: 'right', y: 'bottom' }, + align: { x: 'right', y: 'bottom', margin: 0 }, scale: 0.3, angle: 0, opacity: 0.75, @@ -118,6 +108,17 @@ function createImageLayer(): WatermarkPreset['layers'][number] { }; } +function createQrLayer(): WatermarkPreset['layers'][number] { + return { + id: genId(), + type: 'qr', + data: '', + align: { x: 'right', y: 'bottom', margin: 0 }, + scale: 0.3, + opacity: 1, + }; +} + function createStripeLayer(): WatermarkPreset['layers'][number] { return { id: genId(), @@ -165,7 +166,7 @@ const props = defineProps<{ const preset = reactive<WatermarkPreset>(deepClone(props.preset) ?? { id: genId(), name: '', - layers: [createTextLayer()], + layers: [], }); const emit = defineEmits<{ @@ -187,28 +188,6 @@ async function cancel() { dialog.value?.close(); } -const { - model: type, - def: typeDef, -} = useMkSelect({ - items: [ - { label: i18n.ts._watermarkEditor.text, value: 'text' }, - { label: i18n.ts._watermarkEditor.image, value: 'image' }, - { label: i18n.ts._watermarkEditor.advanced, value: 'advanced' }, - ], - initialValue: preset.layers.length > 1 ? 'advanced' : preset.layers[0].type, -}); - -watch(type, () => { - if (type.value === 'text') { - preset.layers = [createTextLayer()]; - } else if (type.value === 'image') { - preset.layers = [createImageLayer()]; - } else if (type.value === 'advanced') { - // nop - } -}); - watch(preset, async (newValue, oldValue) => { if (renderer != null) { renderer.setLayers(preset.layers); @@ -339,6 +318,11 @@ function addLayer(ev: MouseEvent) { preset.layers.push(createImageLayer()); }, }, { + text: i18n.ts._watermarkEditor.qr, + action: () => { + preset.layers.push(createQrLayer()); + }, + }, { text: i18n.ts._watermarkEditor.stripe, action: () => { preset.layers.push(createStripeLayer()); |