diff --git a/index.html b/index.html index 66c89ee..f912e5b 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,10 @@ + + + RUWIO diff --git a/package.json b/package.json index 3e0e093..a85dcdf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ruwio", "private": true, - "version": "0.11.1", + "version": "0.12.0", "type": "module", "scripts": { "dev": "vite", diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..370b83e --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,32 @@ +{ + "name": "RUWIO", + "short_name": "RUWIO", + "description": "A timeline-based animation editor that runs in your browser.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "display_override": ["window-controls-overlay", "standalone"], + "orientation": "any", + "background_color": "#17171b", + "theme_color": "#17171b", + "icons": [ + { + "src": "/pwa-icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/pwa-icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/pwa-icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/public/pwa-icon-192.png b/public/pwa-icon-192.png new file mode 100644 index 0000000..39db5f1 Binary files /dev/null and b/public/pwa-icon-192.png differ diff --git a/public/pwa-icon-512.png b/public/pwa-icon-512.png new file mode 100644 index 0000000..4ee8e28 Binary files /dev/null and b/public/pwa-icon-512.png differ diff --git a/public/pwa-icon-maskable-512.png b/public/pwa-icon-maskable-512.png new file mode 100644 index 0000000..f316a68 Binary files /dev/null and b/public/pwa-icon-maskable-512.png differ diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..94229c6 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,19 @@ +// Minimal service worker. Present so Chrome considers the site +// installable as a PWA; installed instances run in standalone display +// mode, which removes the top-edge "exit fullscreen" indicator that +// regular-browser tabs get when entering the Fullscreen API. +// No caching logic — assets are served straight from the network so +// a new build is picked up without a version bump dance. + +self.addEventListener('install', (event) => { + self.skipWaiting() +}) + +self.addEventListener('activate', (event) => { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('fetch', () => { + // Fall through to network. Required handler for installability; we + // deliberately do nothing else so dev/prod behave identically. +}) diff --git a/src/App.vue b/src/App.vue index bd70262..7e6218b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -24,6 +24,7 @@ import HelpDialog from './components/HelpDialog.vue' import CanvasStage from './components/CanvasStage.vue' import ClipartDrawer from './components/ClipartDrawer.vue' import SceneDrawer from './components/SceneDrawer.vue' +import DocumentDrawer from './components/DocumentDrawer.vue' import DocumentSettings from './components/DocumentSettings.vue' import ExportDialog from './components/ExportDialog.vue' import FullscreenPreview from './components/FullscreenPreview.vue' @@ -37,7 +38,9 @@ import { confirmDialog, DialogsHost, IconButton, promptDialog, Toast } from './c import { t } from './i18n' import { useHistory } from './composables/useHistory' import { useAudioRuntime } from './composables/useAudioRuntime' +import { useDocumentDrawer } from './composables/useDocumentDrawer' import { usePlaybackLoop } from './composables/usePlaybackLoop' +import { useProjectName } from './composables/useProjectName' import { ProjectFileError, downloadProject, @@ -136,8 +139,8 @@ function onWindowResize() { } const loadError = ref(null) const savedToast = ref(null) -const currentProjectId = ref(null) -const currentProjectName = ref(null) +const { projectName: currentProjectName, projectId: currentProjectId } = useProjectName() +const documentDrawer = useDocumentDrawer() // Dirty = scene has changed since the last save/load. Deep-watches // store.scene; save/load paths clear it after the next tick so the // replacement itself doesn't leave the project flagged as unsaved. @@ -195,32 +198,12 @@ async function saveProject() { await markClean() } -async function renameProject() { - const suggested = - currentProjectName.value ?? - t('app.saveDialog.defaultName', { date: new Date().toLocaleDateString() }) - const entered = await promptDialog({ - title: t('app.renameProjectDialog.title'), - message: t('app.renameProjectDialog.message'), - defaultValue: suggested, - confirmLabel: t('app.renameProjectDialog.confirm'), - }) - if (entered === null) return - const trimmed = entered.trim() - if (!trimmed || trimmed === currentProjectName.value) return - currentProjectName.value = trimmed - // If the project is already persisted in browser storage, sync the - // new name to its record so future loads (and the recents list) use - // it. Unsaved projects just get the in-memory name — the next save - // picks it up. - if (currentProjectId.value) { - const project = JSON.parse(JSON.stringify(store.project)) - const rec = await saveProjectToStore(project, { - id: currentProjectId.value, - name: trimmed, - }) - currentProjectName.value = rec.name - } +function openProjectNameDrawer() { + // Clicking the project name in the topbar toggles the document + // drawer — edits the name inline alongside the rest of the document + // settings. Clicking again closes it, matching the topbar button's + // dual role as trigger and dismiss. + documentDrawer.toggle() } function exportProjectFile() { @@ -423,9 +406,11 @@ onBeforeUnmount(() => { diff --git a/src/components/DocumentSettings.vue b/src/components/DocumentSettings.vue index fa629a7..9377978 100644 --- a/src/components/DocumentSettings.vue +++ b/src/components/DocumentSettings.vue @@ -1,29 +1,24 @@