diff --git a/package.json b/package.json index 596ed7b..4a3eff2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ruwio", "private": true, - "version": "1.1.3", + "version": "1.1.4", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 5f0db0e..ed9613f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -45,11 +45,13 @@ import { ProjectFileError, pickProjectFile } from './lib/projectFile' import { readAnyProjectFile } from './lib/ruwFile' import { getLastOpenedId, + getStoredProject, listRecentProjects, saveProjectToStore, setLastOpenedId, type StoredProject, } from './lib/projectStore' +import { showWelcomeOnStart } from './composables/useShowWelcomeOnStart' import { importImageFile } from './lib/fileLoader' import { sampleLayer } from './lib/interpolate' import { useClipartStore } from './stores/clipart' @@ -270,18 +272,27 @@ function showSavedToast(msg: string) { }, 2500) } +/** Boot policy: always restore the last edited project so the user + * picks up where they left off, regardless of the show-welcome + * preference. The welcome dialog still opens on top when the user + * has the flag enabled — picking a different project there just + * re-loads it through openFromRecent. With NO recents present, + * the dialog opens unconditionally so first-run users see the + * Import / New options. */ async function bootWelcome() { - const recents = await listRecentProjects() - if (recents.length > 0) { - welcomeOpen.value = true - const lastId = getLastOpenedId() - if (lastId) { - const last = recents.find((p) => p.id === lastId) - if (last) { - currentProjectName.value = last.name - } + const lastId = getLastOpenedId() + if (lastId) { + const stored = await getStoredProject(lastId) + if (stored?.project) { + await openFromRecent(stored) } } + const recents = await listRecentProjects() + if (recents.length === 0) { + welcomeOpen.value = true + return + } + if (showWelcomeOnStart.value) welcomeOpen.value = true } function isEditable(el: EventTarget | null): boolean { diff --git a/src/components/DocumentSettings.vue b/src/components/DocumentSettings.vue index 6b0ecb1..6b1eba3 100644 --- a/src/components/DocumentSettings.vue +++ b/src/components/DocumentSettings.vue @@ -11,6 +11,7 @@ import { useTheme } from '@/composables/useTheme' import type { Theme } from '@/composables/useTheme' import { useTooltipPref } from '@/composables/useTooltipPref' import { useAssetThumbsPref } from '@/composables/useAssetThumbsPref' +import { useShowWelcomeOnStart } from '@/composables/useShowWelcomeOnStart' import { showLocked } from '@/composables/useShowLocked' import { Modal } from '@/components/ui' import { setLocale, t, useI18n } from '@/i18n' @@ -23,6 +24,7 @@ const clipart = useClipartStore() const { theme, setTheme } = useTheme() const { enabled: tooltipsEnabled } = useTooltipPref() const { enabled: thumbsEnabled } = useAssetThumbsPref() +const { enabled: welcomeOnStart } = useShowWelcomeOnStart() const { locale } = useI18n() // Re-check the stored clipart folder whenever settings opens — covers the @@ -175,6 +177,16 @@ function applyTheme(t: Theme) { + +
{{ t('settings.appearanceTitle') }}
{{ t('settings.themeHint') }}
diff --git a/src/components/ExportDialog.vue b/src/components/ExportDialog.vue index fffc157..3fd5f49 100644 --- a/src/components/ExportDialog.vue +++ b/src/components/ExportDialog.vue @@ -381,7 +381,7 @@ function onUpdate(v: boolean) { - +
+
+

{{ t('export.colProject') }}

- + - + - + - + +
- +
+

{{ t('export.colMedia') }}

-
-
{{ t('export.targetSize') }}
- - × -
- {{ targetHeight }} - H -
- +
+
{{ t('export.targetSize') }}
+ + × +
+ {{ targetHeight }} + H +
+ +
+ +
+
{{ t('export.targetFps') }}
+ + {{ t('export.targetFpsHint') }} + +
+ + + + + + + + + + +
- -
-
{{ t('export.targetFps') }}
- - {{ t('export.targetFpsHint') }} - -
- - - - - -
@@ -596,6 +606,42 @@ function onUpdate(v: boolean) { padding: 12px; } +/* Two-column layout. The skip-hidden toggle still spans the full + width above; project / data exports live left, media exports + (with the per-export size + fps inputs) live right. Each column + stacks vertically and gaps follow the same 6px rhythm as the + single-column flow used to. */ +.export-cols { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + align-items: start; +} + +.export-col { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; +} + +.col-heading { + margin: 0 0 4px; + font-size: 11px; + font-weight: 500; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.06em; +} + +@media (max-width: 720px) { + /* Stage tight viewports: collapse to a single column so the + options stay readable instead of cramming. */ + .export-cols { + grid-template-columns: 1fr; + } +} + .export-toggle { display: flex; align-items: center; diff --git a/src/components/WelcomeDialog.vue b/src/components/WelcomeDialog.vue index 301351d..6a1a19c 100644 --- a/src/components/WelcomeDialog.vue +++ b/src/components/WelcomeDialog.vue @@ -22,6 +22,7 @@ import { readBackupFile, } from '@/lib/backupFile' import { confirmDialog, Modal } from '@/components/ui' +import { showWelcomeOnStart } from '@/composables/useShowWelcomeOnStart' import { t } from '@/i18n' const props = defineProps<{ modelValue: boolean }>() @@ -33,14 +34,25 @@ const emit = defineEmits<{ }>() const recents = ref([]) +// Archive = the rest of the IDB store beyond the recent cap. +// Rendered as a flat list (no thumbnails) so the user can find old +// projects without scrolling through tile previews. +const archive = ref([]) const loading = ref(false) const recentRefs = ref([]) +const archiveRefs = ref([]) const newBtnRef = ref(null) async function reload() { loading.value = true try { - recents.value = await listRecentProjects() + const [topRecent, all] = await Promise.all([ + listRecentProjects(), + listAllProjects(), + ]) + recents.value = topRecent + const recentIds = new Set(topRecent.map((p) => p.id)) + archive.value = all.filter((p) => !recentIds.has(p.id)) } finally { loading.value = false } @@ -261,7 +273,7 @@ watch(