diff --git a/package.json b/package.json index 0d2efe8..26a87a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "animatr", + "name": "ruwio", "private": true, - "version": "0.10.7", + "version": "0.11.0", "type": "module", "scripts": { "dev": "vite", diff --git a/scripts/bump.sh b/scripts/bump.sh new file mode 100755 index 0000000..16da542 --- /dev/null +++ b/scripts/bump.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Bump the version in package.json. Usage: +# +# bash scripts/bump.sh patch # 0.10.7 -> 0.10.8 +# bash scripts/bump.sh minor # 0.10.7 -> 0.11.0 +# bash scripts/bump.sh major # 0.10.7 -> 1.0.0 +set -euo pipefail + +cd "$(dirname "$0")/.." + +KIND="${1:-}" +case "$KIND" in + patch|minor|major) ;; + *) + echo "Usage: $0 {patch|minor|major}" >&2 + exit 1 + ;; +esac + +NEW_VERSION=$(node -e " + const fs = require('fs'); + const pkgPath = './package.json'; + const verPath = './src/version.ts'; + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + const m = /^(\d+)\.(\d+)\.(\d+)\$/.exec(pkg.version); + if (!m) { console.error('unparseable version:', pkg.version); process.exit(1); } + let [_, a, b, c] = m.map((x, i) => i === 0 ? x : Number(x)); + if (process.argv[1] === 'patch') c += 1; + else if (process.argv[1] === 'minor') { b += 1; c = 0; } + else if (process.argv[1] === 'major') { a += 1; b = 0; c = 0; } + const next = a + '.' + b + '.' + c; + pkg.version = next; + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n'); + const ver = fs.readFileSync(verPath, 'utf8'); + const updated = ver.replace( + /(APP_VERSION\s*=\s*)(['\"])[^'\"]*\2/, + (_m, lhs, q) => lhs + q + next + q, + ); + if (updated === ver) { console.error('APP_VERSION not found in', verPath); process.exit(1); } + fs.writeFileSync(verPath, updated); + process.stdout.write(next); +" "$KIND") + +echo "Bumped to $NEW_VERSION" diff --git a/src/App.vue b/src/App.vue index c49baf2..bd70262 100644 --- a/src/App.vue +++ b/src/App.vue @@ -13,12 +13,14 @@ import { PhPause, PhPencilSimpleLine, PhPlay, + PhQuestion, PhSkipBack, PhSparkle, PhStop, } from '@phosphor-icons/vue' import AboutDialog from './components/AboutDialog.vue' import AssetSidebar from './components/AssetSidebar.vue' +import HelpDialog from './components/HelpDialog.vue' import CanvasStage from './components/CanvasStage.vue' import ClipartDrawer from './components/ClipartDrawer.vue' import SceneDrawer from './components/SceneDrawer.vue' @@ -59,6 +61,7 @@ const testsOpen = ref(false) const welcomeOpen = ref(false) const fullscreenOpen = ref(false) const aboutOpen = ref(false) +const helpOpen = ref(false) const TIMELINE_HEIGHT_KEY = 'animatr:timelineHeight' const DEFAULT_TIMELINE_HEIGHT = 220 const MIN_TIMELINE_HEIGHT = 120 @@ -501,6 +504,9 @@ onBeforeUnmount(() => { + @@ -556,6 +562,7 @@ onBeforeUnmount(() => { + () const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>() @@ -28,6 +28,7 @@ const clipart = useClipartStore() const { theme, setTheme } = useTheme() const { enabled: tooltipsEnabled } = useTooltipPref() const { enabled: thumbsEnabled } = useAssetThumbsPref() +const { locale } = useI18n() // Re-check the stored clipart folder whenever settings opens — covers the // case where initial App.vue init raced ahead of IDB or the user opens @@ -39,13 +40,12 @@ watch( }, ) -type TabId = 'document' | 'clipart' | 'interface' | 'theme' +type TabId = 'document' | 'clipart' | 'interface' -const tabs: { id: TabId; label: string; icon: typeof PhFrameCorners }[] = [ - { id: 'document', label: t('settings.tab.document'), icon: PhFrameCorners }, - { id: 'clipart', label: t('settings.tab.clipart'), icon: PhFolder }, - { id: 'interface', label: t('settings.tab.interface'), icon: PhSlidersHorizontal }, - { id: 'theme', label: t('settings.tab.theme'), icon: PhPalette }, +const tabs: { id: TabId; labelKey: string; icon: typeof PhFrameCorners }[] = [ + { id: 'document', labelKey: 'settings.tab.document', icon: PhFrameCorners }, + { id: 'clipart', labelKey: 'settings.tab.clipart', icon: PhFolder }, + { id: 'interface', labelKey: 'settings.tab.interface', icon: PhSlidersHorizontal }, ] const activeTab = ref('document') @@ -195,6 +195,11 @@ const fps = computed({ set: (v: number) => store.setFps(v), }) +const selectedLocale = computed({ + get: () => locale.value, + set: (value: Locale) => setLocale(value), +}) + const presets: { label: string; w: number; h: number }[] = [ { label: '16:9 · 1280×720', w: 1280, h: 720 }, { label: '16:9 · 1920×1080', w: 1920, h: 1080 }, @@ -221,6 +226,15 @@ function applyTheme(t: Theme) { :model-value="modelValue" :title="t('settings.title')" size="lg" + :dialog-style="{ + width: 'min(920px, calc(100% - 32px))', + height: 'min(620px, calc(100vh - 48px))', + }" + :body-style="{ + display: 'flex', + overflow: 'hidden', + minHeight: 0, + }" @update:model-value="emit('update:modelValue', $event)" >
@@ -233,7 +247,7 @@ function applyTheme(t: Theme) { @click="activeTab = tab.id" > - {{ tab.label }} + {{ t(tab.labelKey) }} @@ -362,6 +376,18 @@ function applyTheme(t: Theme) {

{{ t('settings.tab.interface') }}

+
+
{{ t('settings.languageTitle') }}
+
{{ t('settings.languageDesc') }}
+ +
+
- -
-

{{ t('settings.tab.theme') }}

-
- - +
+
{{ t('settings.appearanceTitle') }}
+
{{ t('settings.themeHint') }}
+
+ + +
-

{{ t('settings.themeHint') }}

@@ -438,7 +464,8 @@ function applyTheme(t: Theme) { diff --git a/src/components/ui/Modal.vue b/src/components/ui/Modal.vue index bd42efe..330555f 100644 --- a/src/components/ui/Modal.vue +++ b/src/components/ui/Modal.vue @@ -1,9 +1,10 @@