Wire metadata previews through ExifTool store with friendly status messages.
Prefetch ExifTool on upload mount and track pending/loading/ready per file for batch preview loads.
This commit is contained in:
@@ -1,21 +1,20 @@
|
|||||||
import { parseMetadata } from '@uswriting/exiftool';
|
import { defineStore, storeToRefs } from 'pinia';
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useExiftoolStore } from '@/stores/exiftool';
|
||||||
import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview';
|
import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview';
|
||||||
import { resolveErrorMessage } from '@/utils/resolveErrorMessage';
|
|
||||||
|
const EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool';
|
||||||
|
const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool';
|
||||||
|
const METADATA_READING_MESSAGE = 'Reading metadata';
|
||||||
|
const METADATA_READ_ERROR_MESSAGE = 'Could not read image metadata';
|
||||||
|
|
||||||
export const useMetadataStore = defineStore('metadata', () => {
|
export const useMetadataStore = defineStore('metadata', () => {
|
||||||
const previewsByKey = ref<Record<string, MetadataPreviewEntry>>({});
|
const previewsByKey = ref<Record<string, MetadataPreviewEntry>>({});
|
||||||
|
|
||||||
const readExifToolStdout = async (file: File): Promise<string> => {
|
const exiftoolStore = useExiftoolStore();
|
||||||
const result = await parseMetadata(file);
|
|
||||||
|
|
||||||
if (!result.success) {
|
const { initStatus: exiftoolInitStatus } = storeToRefs(exiftoolStore);
|
||||||
throw new Error(result.error);
|
const { initializeExiftool, readMetadata } = exiftoolStore;
|
||||||
}
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeKeyFromFile = (file: File): string => `${file.name}\0${file.size}\0${file.lastModified}`;
|
const makeKeyFromFile = (file: File): string => `${file.name}\0${file.size}\0${file.lastModified}`;
|
||||||
|
|
||||||
@@ -28,20 +27,59 @@ export const useMetadataStore = defineStore('metadata', () => {
|
|||||||
const loadPreview = async (file: File): Promise<MetadataPreviewEntry> => {
|
const loadPreview = async (file: File): Promise<MetadataPreviewEntry> => {
|
||||||
const key = makeKeyFromFile(file);
|
const key = makeKeyFromFile(file);
|
||||||
|
|
||||||
previewsByKey.value = { ...previewsByKey.value, [key]: { status: 'loading' } };
|
if (exiftoolInitStatus.value === 'error') {
|
||||||
|
const entry: MetadataPreviewEntry = {
|
||||||
|
status: 'error',
|
||||||
|
message: EXIFTOOL_PREPARE_ERROR_MESSAGE
|
||||||
|
};
|
||||||
|
|
||||||
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exiftoolInitStatus.value === 'ready') {
|
||||||
|
previewsByKey.value = {
|
||||||
|
...previewsByKey.value,
|
||||||
|
[key]: { status: 'loading', message: METADATA_READING_MESSAGE }
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
previewsByKey.value = {
|
||||||
|
...previewsByKey.value,
|
||||||
|
[key]: { status: 'pending', message: EXIFTOOL_INITIALIZING_MESSAGE }
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await initializeExiftool();
|
||||||
|
|
||||||
|
previewsByKey.value = {
|
||||||
|
...previewsByKey.value,
|
||||||
|
[key]: { status: 'loading', message: METADATA_READING_MESSAGE }
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
const entry: MetadataPreviewEntry = {
|
||||||
|
status: 'error',
|
||||||
|
message: EXIFTOOL_PREPARE_ERROR_MESSAGE
|
||||||
|
};
|
||||||
|
|
||||||
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const output = await readExifToolStdout(file);
|
const output = await readMetadata(file);
|
||||||
|
|
||||||
const entry: MetadataPreviewEntry = { status: 'ready', output };
|
const entry: MetadataPreviewEntry = { status: 'ready', output };
|
||||||
|
|
||||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
} catch (error) {
|
} catch {
|
||||||
const entry: MetadataPreviewEntry = {
|
const entry: MetadataPreviewEntry = {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
message: resolveErrorMessage(error, 'Could not read image metadata')
|
message: METADATA_READ_ERROR_MESSAGE
|
||||||
};
|
};
|
||||||
|
|
||||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||||
@@ -50,9 +88,8 @@ export const useMetadataStore = defineStore('metadata', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPreviews = async (files: readonly File[]): Promise<void> => {
|
const loadPreviews = (files: readonly File[]): Promise<MetadataPreviewEntry[]> =>
|
||||||
await Promise.all(files.map(file => loadPreview(file)));
|
Promise.all(files.map(file => loadPreview(file)));
|
||||||
};
|
|
||||||
|
|
||||||
const clearPreview = (file: File): void => {
|
const clearPreview = (file: File): void => {
|
||||||
const key = makeKeyFromFile(file);
|
const key = makeKeyFromFile(file);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export type MetadataPreviewEntry =
|
export type MetadataPreviewEntry =
|
||||||
| { status: 'idle' }
|
| { status: 'idle' }
|
||||||
| { status: 'loading' }
|
| { status: 'pending'; message: string }
|
||||||
|
| { status: 'loading'; message: string }
|
||||||
| { status: 'ready'; output: string }
|
| { status: 'ready'; output: string }
|
||||||
| { status: 'error'; message: string };
|
| { status: 'error'; message: string };
|
||||||
|
|||||||
@@ -4,3 +4,16 @@
|
|||||||
<p class="mt-2 text-muted-foreground">Upload UI coming soon.</p>
|
<p class="mt-2 text-muted-foreground">Upload UI coming soon.</p>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeMount } from 'vue';
|
||||||
|
import { useExiftoolStore } from '@/stores/exiftool';
|
||||||
|
|
||||||
|
const exiftoolStore = useExiftoolStore();
|
||||||
|
|
||||||
|
const { initializeExiftool } = exiftoolStore;
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initializeExiftool();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user