wip
This commit is contained in:
parent
e4b6853f0a
commit
6becc489dc
|
|
@ -31,15 +31,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<div :class="$style.controls">
|
||||
<div class="_spacer _gaps">
|
||||
<MkRange v-model="frame.borderThickness" :min="0" :max="0.2" :step="0.01" :continuousUpdate="true">
|
||||
<MkRange v-model="params.borderThickness" :min="0" :max="0.2" :step="0.01" :continuousUpdate="true">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.borderThickness }}</template>
|
||||
</MkRange>
|
||||
|
||||
<MkInput :modelValue="getHex(frame.bgColor)" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) frame.bgColor = c; }">
|
||||
<MkInput :modelValue="getHex(params.bgColor)" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) params.bgColor = c; }">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.backgroundColor }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput :modelValue="getHex(frame.fgColor)" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) frame.fgColor = c; }">
|
||||
<MkInput :modelValue="getHex(params.fgColor)" type="color" @update:modelValue="v => { const c = getRgb(v); if (c != null) params.fgColor = c; }">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.textColor }}</template>
|
||||
</MkInput>
|
||||
|
||||
|
|
@ -47,31 +47,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts._imageFrameEditor.header }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="frame.labelTop.enabled">
|
||||
<MkSwitch v-model="params.labelTop.enabled">
|
||||
<template #label>{{ i18n.ts.show }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkRange v-model="frame.labelTop.padding" :min="0.01" :max="0.5" :step="0.01" :continuousUpdate="true">
|
||||
<MkRange v-model="params.labelTop.padding" :min="0.01" :max="0.5" :step="0.01" :continuousUpdate="true">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.labelThickness }}</template>
|
||||
</MkRange>
|
||||
|
||||
<MkRange v-model="frame.labelTop.scale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
||||
<MkRange v-model="params.labelTop.scale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.labelScale }}</template>
|
||||
</MkRange>
|
||||
|
||||
<MkSwitch v-model="frame.labelTop.centered">
|
||||
<MkSwitch v-model="params.labelTop.centered">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.centered }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkInput v-model="frame.labelTop.textBig">
|
||||
<MkInput v-model="params.labelTop.textBig">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.captionMain }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkTextarea v-model="frame.labelTop.textSmall">
|
||||
<MkTextarea v-model="params.labelTop.textSmall">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.captionSub }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkSwitch v-model="frame.labelTop.withQrCode">
|
||||
<MkSwitch v-model="params.labelTop.withQrCode">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.withQrCode }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
|
|
@ -81,31 +81,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts._imageFrameEditor.footer }}</template>
|
||||
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="frame.labelBottom.enabled">
|
||||
<MkSwitch v-model="params.labelBottom.enabled">
|
||||
<template #label>{{ i18n.ts.show }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkRange v-model="frame.labelBottom.padding" :min="0.01" :max="0.5" :step="0.01" :continuousUpdate="true">
|
||||
<MkRange v-model="params.labelBottom.padding" :min="0.01" :max="0.5" :step="0.01" :continuousUpdate="true">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.labelThickness }}</template>
|
||||
</MkRange>
|
||||
|
||||
<MkRange v-model="frame.labelBottom.scale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
||||
<MkRange v-model="params.labelBottom.scale" :min="0.5" :max="2.0" :step="0.01" :continuousUpdate="true">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.labelScale }}</template>
|
||||
</MkRange>
|
||||
|
||||
<MkSwitch v-model="frame.labelBottom.centered">
|
||||
<MkSwitch v-model="params.labelBottom.centered">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.centered }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkInput v-model="frame.labelBottom.textBig">
|
||||
<MkInput v-model="params.labelBottom.textBig">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.captionMain }}</template>
|
||||
</MkInput>
|
||||
|
||||
<MkTextarea v-model="frame.labelBottom.textSmall">
|
||||
<MkTextarea v-model="params.labelBottom.textSmall">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.captionSub }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<MkSwitch v-model="frame.labelBottom.withQrCode">
|
||||
<MkSwitch v-model="params.labelBottom.withQrCode">
|
||||
<template #label>{{ i18n.ts._imageFrameEditor.withQrCode }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
|
|
@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive, nextTick } from 'vue';
|
||||
import ExifReader from 'exifreader';
|
||||
import { throttle } from 'throttle-debounce';
|
||||
import type { ImageFrameParams } from '@/utility/image-frame-renderer/image-frame-renderer.js';
|
||||
import type { ImageFrameParams, ImageFramePreset } from '@/utility/image-frame-renderer/image-frame-renderer.js';
|
||||
import { ImageFrameRenderer } from '@/utility/image-frame-renderer/image-frame-renderer.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
|
|
@ -153,22 +153,19 @@ import { useMkSelect } from '@/composables/use-mkselect.js';
|
|||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const EXIF_MOCK = {
|
||||
DateTimeOriginal: { description: '2012:03:04 5:06:07' },
|
||||
Model: { description: 'Example camera' },
|
||||
LensModel: { description: 'Example lens 123mm f/1.23' },
|
||||
FocalLength: { description: '123mm' },
|
||||
ExposureTime: { description: '1/234' },
|
||||
FNumber: { description: '1.23' },
|
||||
ISOSpeedRatings: { description: '123' },
|
||||
} satisfies ExifReader.Tags;
|
||||
|
||||
const props = defineProps<{
|
||||
frame?: ImageFrameParams | null;
|
||||
presetEditMode?: boolean;
|
||||
preset?: ImageFramePreset | null;
|
||||
params?: ImageFrameParams | null;
|
||||
image?: File | null;
|
||||
}>();
|
||||
|
||||
const frame = reactive<ImageFrameParams>(deepClone(props.frame) ?? {
|
||||
const preset = deepClone(props.preset) ?? {
|
||||
id: genId(),
|
||||
name: '',
|
||||
};
|
||||
|
||||
const params = reactive<ImageFrameParams>(deepClone(props.params) ?? {
|
||||
borderThickness: 0.05,
|
||||
labelTop: {
|
||||
enabled: false,
|
||||
|
|
@ -194,6 +191,7 @@ const frame = reactive<ImageFrameParams>(deepClone(props.frame) ?? {
|
|||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'ok', frame: ImageFrameParams): void;
|
||||
(ev: 'presetOk', preset: ImageFramePreset): void;
|
||||
(ev: 'cancel'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
|
@ -206,11 +204,11 @@ async function cancel() {
|
|||
|
||||
const updateThrottled = throttle(50, () => {
|
||||
if (renderer != null) {
|
||||
renderer.render(frame);
|
||||
renderer.render(params);
|
||||
}
|
||||
});
|
||||
|
||||
watch(frame, async (newValue, oldValue) => {
|
||||
watch(params, async (newValue, oldValue) => {
|
||||
updateThrottled();
|
||||
}, { deep: true });
|
||||
|
||||
|
|
@ -262,14 +260,14 @@ async function initRenderer() {
|
|||
renderer = new ImageFrameRenderer({
|
||||
canvas: canvasEl.value,
|
||||
image: sampleImage_3_2,
|
||||
exif: EXIF_MOCK,
|
||||
exif: null,
|
||||
renderAsPreview: true,
|
||||
});
|
||||
} else if (sampleImageType.value === '2_3') {
|
||||
renderer = new ImageFrameRenderer({
|
||||
canvas: canvasEl.value,
|
||||
image: sampleImage_2_3,
|
||||
exif: EXIF_MOCK,
|
||||
exif: null,
|
||||
renderAsPreview: true,
|
||||
});
|
||||
} else if (imageFile != null) {
|
||||
|
|
@ -285,7 +283,7 @@ async function initRenderer() {
|
|||
});
|
||||
}
|
||||
|
||||
await renderer!.render(frame);
|
||||
await renderer!.render(params);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -313,21 +311,34 @@ 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('presetOk', {
|
||||
...preset,
|
||||
params: deepClone(params),
|
||||
});
|
||||
} else {
|
||||
dialog.value?.close();
|
||||
if (renderer != null) {
|
||||
renderer.destroy();
|
||||
renderer = null;
|
||||
}
|
||||
|
||||
emit('ok', params);
|
||||
}
|
||||
|
||||
emit('ok', preset);
|
||||
}
|
||||
|
||||
function getHex(c: [number, number, number]) {
|
||||
|
|
|
|||
|
|
@ -343,8 +343,8 @@ export function useUploader(options: {
|
|||
!item.uploading &&
|
||||
!item.uploaded
|
||||
) {
|
||||
function changePreset(preset: ImageFramePreset | null) {
|
||||
item.imageFramePreset = preset;
|
||||
function change(params: ImageFrameParams | null) {
|
||||
item.imageFrameParams = params;
|
||||
preprocess(item).then(() => {
|
||||
triggerRef(items);
|
||||
});
|
||||
|
|
@ -355,35 +355,41 @@ export function useUploader(options: {
|
|||
text: i18n.ts.frame,
|
||||
type: 'parent',
|
||||
children: [{
|
||||
type: 'radioOption',
|
||||
text: i18n.ts.none,
|
||||
active: computed(() => item.imageFrameParams == null),
|
||||
action: () => changePreset(null),
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, ...prefer.s.imageFramePresets.map(preset => ({
|
||||
type: 'radioOption' as const,
|
||||
text: preset.name,
|
||||
active: computed(() => item.imageFramePreset?.id === preset.id),
|
||||
action: () => changePreset(preset),
|
||||
})), ...(prefer.s.imageFramePresets.length > 0 ? [{
|
||||
type: 'divider' as const,
|
||||
}] : []), {
|
||||
type: 'button',
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.add,
|
||||
icon: 'ti ti-pencil',
|
||||
text: i18n.ts.edit,
|
||||
action: async () => {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), {
|
||||
params: item.imageFrameParams,
|
||||
image: item.file,
|
||||
}, {
|
||||
ok: (preset) => {
|
||||
prefer.commit('imageFramePresets', [...prefer.s.imageFramePresets, preset]);
|
||||
changePreset(preset.id);
|
||||
ok: (params) => {
|
||||
change(params);
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
}],
|
||||
}, {
|
||||
type: 'button',
|
||||
text: i18n.ts.remove,
|
||||
action: () => change(null),
|
||||
}, {
|
||||
type: 'divider',
|
||||
}, ...prefer.s.imageFramePresets.map(preset => ({
|
||||
type: 'button' as const,
|
||||
text: preset.name,
|
||||
action: async () => {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), {
|
||||
params: preset.params,
|
||||
image: item.file,
|
||||
}, {
|
||||
ok: (params) => {
|
||||
change(params);
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
}))],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -625,33 +631,28 @@ export function useUploader(options: {
|
|||
});
|
||||
}
|
||||
|
||||
const canvas = window.document.createElement('canvas');
|
||||
const needsImageFrame = item.imageFrameParams != null && IMAGE_EDITING_SUPPORTED_TYPES.includes(preprocessedFile.type);
|
||||
if (needsImageFrame && item.imageFrameParams != null) {
|
||||
const canvas = window.document.createElement('canvas');
|
||||
const exif = await ExifReader.load(await item.file.arrayBuffer());
|
||||
const frameRenderer = new ImageFrameRenderer({
|
||||
canvas: canvas,
|
||||
image: await window.createImageBitmap(preprocessedFile),
|
||||
exif,
|
||||
});
|
||||
|
||||
const exif = await ExifReader.load(await item.file.arrayBuffer());
|
||||
await frameRenderer.render(item.imageFrameParams);
|
||||
|
||||
const frameRenderer = new ImageFrameRenderer({
|
||||
canvas: canvas,
|
||||
image: await window.createImageBitmap(preprocessedFile),
|
||||
exif,
|
||||
});
|
||||
//await frameRenderer.update({
|
||||
// title: `${meta_model} + ${meta_lensModel}`,
|
||||
// text: `${date} ${meta_mm}mm f/${meta_f} ${meta_s}s ISO${meta_iso}`,
|
||||
//});
|
||||
await frameRenderer.render({
|
||||
title: 'aaaaaaaaaaaaa',
|
||||
text: 'bbbbbbbbbbbbbbbbbbbb',
|
||||
});
|
||||
|
||||
preprocessedFile = await new Promise<Blob>((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob == null) {
|
||||
throw new Error('Failed to convert canvas to blob');
|
||||
}
|
||||
resolve(blob);
|
||||
frameRenderer.destroy();
|
||||
}, 'image/png');
|
||||
});
|
||||
preprocessedFile = await new Promise<Blob>((resolve) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob == null) {
|
||||
throw new Error('Failed to convert canvas to blob');
|
||||
}
|
||||
resolve(blob);
|
||||
frameRenderer.destroy();
|
||||
}, 'image/png');
|
||||
});
|
||||
}
|
||||
|
||||
const compressionSettings = getCompressionSettings(item.compressionLevel);
|
||||
const needsCompress = item.compressionLevel !== 0 && compressionSettings && IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type) && !(await isAnimated(preprocessedFile));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkFolder :defaultOpen="false" :canPage="false">
|
||||
<template #icon><i class="ti ti-pencil"></i></template>
|
||||
<template #label>{{ i18n.ts.preset }}: {{ preset.name === '' ? '(' + i18n.ts.noName + ')' : preset.name }}</template>
|
||||
<template #footer>
|
||||
<div class="_buttons">
|
||||
<MkButton @click="edit"><i class="ti ti-pencil"></i> {{ i18n.ts.edit }}</MkButton>
|
||||
<MkButton danger iconOnly style="margin-left: auto;" @click="del"><i class="ti ti-trash"></i></MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<canvas ref="canvasEl" :class="$style.previewCanvas"></canvas>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
|
||||
import type { ImageFramePreset } from '@/utility/image-frame-renderer/image-frame-renderer.js';
|
||||
import { ImageFrameRenderer } from '@/utility/image-frame-renderer/image-frame-renderer.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
preset: ImageFramePreset;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'updatePreset', preset: ImageFramePreset): void,
|
||||
(ev: 'del'): void,
|
||||
}>();
|
||||
|
||||
async function edit() {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkImageFrameEditorDialog.vue')), {
|
||||
presetEditMode: true,
|
||||
preset: deepClone(props.preset),
|
||||
params: deepClone(props.preset.params),
|
||||
}, {
|
||||
presetOk: (preset) => {
|
||||
emit('updatePreset', preset);
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
function del(ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.delete,
|
||||
action: () => {
|
||||
emit('del');
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
const canvasEl = useTemplateRef('canvasEl');
|
||||
|
||||
const sampleImage = new Image();
|
||||
sampleImage.src = '/client-assets/sample/3-2.jpg';
|
||||
|
||||
let renderer: ImageFrameRenderer | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
sampleImage.onload = async () => {
|
||||
watch(canvasEl, async () => {
|
||||
if (canvasEl.value == null) return;
|
||||
|
||||
renderer = new ImageFrameRenderer({
|
||||
canvas: canvasEl.value,
|
||||
image: sampleImage,
|
||||
exif: null,
|
||||
renderAsPreview: true,
|
||||
});
|
||||
|
||||
await renderer.render(props.preset.params);
|
||||
}, { immediate: true });
|
||||
};
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (renderer != null) {
|
||||
renderer.destroy();
|
||||
renderer = null;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.preset, async () => {
|
||||
if (renderer != null) {
|
||||
await renderer.render(props.preset.params);
|
||||
}
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.previewCanvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -132,38 +132,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<div class="_gaps">
|
||||
<div class="_gaps_s">
|
||||
<!--
|
||||
<XWatermarkItem
|
||||
v-for="(preset, i) in prefer.r.watermarkPresets.value"
|
||||
<XImageFrameItem
|
||||
v-for="(preset, i) in prefer.r.imageFramePresets.value"
|
||||
:key="preset.id"
|
||||
:preset="preset"
|
||||
@updatePreset="onUpdateWatermarkPreset(preset.id, $event)"
|
||||
@del="onDeleteWatermarkPreset(preset.id)"
|
||||
@updatePreset="onUpdateImageFramePreset(preset.id, $event)"
|
||||
@del="onDeleteImageFramePreset(preset.id)"
|
||||
/>
|
||||
-->
|
||||
|
||||
<MkButton iconOnly rounded style="margin: 0 auto;" @click="addImageFramePreset"><i class="ti ti-plus"></i></MkButton>
|
||||
|
||||
<!--
|
||||
<SearchMarker :keywords="['sync', 'watermark', 'preset', 'devices']">
|
||||
<MkSwitch :modelValue="watermarkPresetsSyncEnabled" @update:modelValue="changeWatermarkPresetsSyncEnabled">
|
||||
<SearchMarker :keywords="['sync', 'frame', 'label', 'preset', 'devices']">
|
||||
<MkSwitch :modelValue="imageFramePresetsSyncEnabled" @update:modelValue="changeImageFramePresetsSyncEnabled">
|
||||
<template #label><i class="ti ti-cloud-cog"></i> <SearchLabel>{{ i18n.ts.syncBetweenDevices }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!--
|
||||
<SearchMarker :keywords="['default', 'label', 'preset']">
|
||||
<MkPreferenceContainer k="defaultWatermarkPresetId">
|
||||
<MkSelect v-model="defaultWatermarkPresetId" :items="[{ label: i18n.ts.none, value: null }, ...prefer.r.watermarkPresets.value.map(p => ({ label: p.name || i18n.ts.noName, value: p.id }))]">
|
||||
<template #label><SearchLabel>{{ i18n.ts.defaultPreset }}</SearchLabel></template>
|
||||
</MkSelect>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
-->
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
|
@ -219,7 +203,9 @@ import { computed, defineAsyncComponent, ref } from 'vue';
|
|||
import * as Misskey from 'misskey-js';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import XWatermarkItem from './drive.WatermarkItem.vue';
|
||||
import XImageFrameItem from './drive.ImageFrameItem.vue';
|
||||
import type { WatermarkPreset } from '@/utility/watermark.js';
|
||||
import type { ImageFramePreset } from '@/utility/image-frame-renderer/image-frame-renderer.js';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
|
|
@ -239,6 +225,7 @@ import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
|||
import { selectDriveFolder } from '@/utility/drive.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { genId } from '@/utility/id.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
|
|
@ -280,6 +267,20 @@ function changeWatermarkPresetsSyncEnabled(value: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
const imageFramePresetsSyncEnabled = ref(prefer.isSyncEnabled('imageFramePresets'));
|
||||
|
||||
function changeImageFramePresetsSyncEnabled(value: boolean) {
|
||||
if (value) {
|
||||
prefer.enableSync('imageFramePresets').then((res) => {
|
||||
if (res == null) return;
|
||||
if (res.enabled) imageFramePresetsSyncEnabled.value = true;
|
||||
});
|
||||
} else {
|
||||
prefer.disableSync('imageFramePresets');
|
||||
imageFramePresetsSyncEnabled.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
misskeyApi('drive').then(info => {
|
||||
capacity.value = info.capacity;
|
||||
usage.value = info.usage;
|
||||
|
|
@ -343,11 +344,35 @@ function onDeleteWatermarkPreset(id: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function onUpdateImageFramePreset(id: string, preset: ImageFramePreset) {
|
||||
const index = prefer.s.imageFramePresets.findIndex(p => p.id === id);
|
||||
if (index !== -1) {
|
||||
prefer.commit('imageFramePresets', [
|
||||
...prefer.s.imageFramePresets.slice(0, index),
|
||||
preset,
|
||||
...prefer.s.imageFramePresets.slice(index + 1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function onDeleteImageFramePreset(id: string) {
|
||||
const index = prefer.s.imageFramePresets.findIndex(p => p.id === id);
|
||||
if (index !== -1) {
|
||||
prefer.commit('imageFramePresets', [
|
||||
...prefer.s.imageFramePresets.slice(0, index),
|
||||
...prefer.s.imageFramePresets.slice(index + 1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
async function addImageFramePreset() {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageFrameEditorDialog.vue').then(x => x.default), {
|
||||
presetEditMode: true,
|
||||
preset: null,
|
||||
params: null,
|
||||
}, {
|
||||
ok: (preset: any) => {
|
||||
//prefer.commit('imageFramePresets', [...prefer.s.imageFramePresets, preset]);
|
||||
presetOk: (preset) => {
|
||||
prefer.commit('imageFramePresets', [...prefer.s.imageFramePresets, preset]);
|
||||
},
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,16 @@ export type ImageFramePreset = {
|
|||
params: ImageFrameParams;
|
||||
};
|
||||
|
||||
const EXIF_MOCK = {
|
||||
DateTimeOriginal: { description: '2012:03:04 5:06:07' },
|
||||
Model: { description: 'Example camera' },
|
||||
LensModel: { description: 'Example lens 123mm f/1.23' },
|
||||
FocalLength: { description: '123mm' },
|
||||
ExposureTime: { description: '1/234' },
|
||||
FNumber: { description: '1.23' },
|
||||
ISOSpeedRatings: { description: '123' },
|
||||
} satisfies ExifReader.Tags;
|
||||
|
||||
export class ImageFrameRenderer {
|
||||
private compositor: ImageCompositor;
|
||||
private image: HTMLImageElement | ImageBitmap;
|
||||
|
|
@ -47,11 +57,11 @@ export class ImageFrameRenderer {
|
|||
constructor(options: {
|
||||
canvas: HTMLCanvasElement,
|
||||
image: HTMLImageElement | ImageBitmap,
|
||||
exif: ExifReader.Tags,
|
||||
exif: ExifReader.Tags | null,
|
||||
renderAsPreview?: boolean,
|
||||
}) {
|
||||
this.image = options.image;
|
||||
this.exif = options.exif;
|
||||
this.exif = options.exif ?? EXIF_MOCK;
|
||||
this.renderAsPreview = options.renderAsPreview ?? false;
|
||||
console.log(this.exif);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue