diff --git a/package.json b/package.json index 138e131..5d32ba0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "animatr", "private": true, - "version": "0.8.1", + "version": "0.8.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 0309a69..ca7509b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -218,8 +218,8 @@ async function renameProject() { function exportProjectFile() { const scene = JSON.parse(JSON.stringify(store.scene)) - const base = currentProjectName.value ?? 'animatr' - const safe = base.replace(/[^a-z0-9äöüß_-]+/gi, '-').replace(/^-+|-+$/g, '') || 'animatr' + const base = currentProjectName.value ?? 'ruwio' + const safe = base.replace(/[^a-z0-9äöüß_-]+/gi, '-').replace(/^-+|-+$/g, '') || 'ruwio' downloadProject(scene, `${safe}.json`) } diff --git a/src/components/CanvasStage.vue b/src/components/CanvasStage.vue index f3c57f4..4478767 100644 --- a/src/components/CanvasStage.vue +++ b/src/components/CanvasStage.vue @@ -2597,9 +2597,30 @@ let panStartViewX = 0 let panStartViewY = 0 let panPointerId = -1 +function isInsideCanvasBounds(e: PointerEvent | MouseEvent): boolean { + const rect = container.value?.getBoundingClientRect() + if (!rect) return true + const localX = e.clientX - rect.left + const localY = e.clientY - rect.top + const cw = effectiveCanvas.value.width * zoom.value + const ch = effectiveCanvas.value.height * zoom.value + return ( + localX >= viewX.value && + localX <= viewX.value + cw && + localY >= viewY.value && + localY <= viewY.value + ch + ) +} + function isPanTrigger(e: PointerEvent | MouseEvent) { if (e.button === 1) return true if (e.button === 0 && e.ctrlKey) return true + // Plain left-click on the empty content view (outside the canvas + // rectangle) pans the viewport too — saves a ctrl-modifier for the + // common case of re-centering after zoom. + if (e.button === 0 && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && !isInsideCanvasBounds(e)) { + return true + } return false } diff --git a/src/components/ExportDialog.vue b/src/components/ExportDialog.vue index d0157d5..b16bedf 100644 --- a/src/components/ExportDialog.vue +++ b/src/components/ExportDialog.vue @@ -135,7 +135,7 @@ function onUpdate(v: boolean) {
- animatr-runtime.js + ruwio-runtime.js {{ t('export.runtimeBadge') }}
{{ t('export.runtimeDesc') }}
diff --git a/src/devtools/tests.ts b/src/devtools/tests.ts index d867d38..87c2ebd 100644 --- a/src/devtools/tests.ts +++ b/src/devtools/tests.ts @@ -13,6 +13,7 @@ import { isLayerActiveAt, sampleLayer, lerpColor } from '@/lib/interpolate' import { snapToCandidates } from '@/lib/timelineSnap' import { generateBundledScript, + generateExampleHtml, generateRuntimeScript, generateSceneScript, } from '@/lib/exportPlayer' @@ -699,6 +700,40 @@ export function registerAllTests(): void { // And the current fallback must be scene.duration. expect(r).toContain('scene.outTime==null?scene.duration:scene.outTime') }) + + test('export comments and console prefix use RUWIO, not Animatr', () => { + // Rebrand: generated comment headers, the runtime's console warn + // prefix, and the example-HTML chrome all read RUWIO now. If any + // of these come back as "Animatr", we regressed. + const s = makeFreshStore() + const runtime = generateRuntimeScript() + expect(runtime).toContain('RUWIO runtime') + expect(runtime.indexOf('/* Animatr ')).toBe(-1) + // Console prefix used by the runtime's action evaluator. + expect(runtime).toContain("'[ruwio]'") + expect(runtime.indexOf("'[animatr]'")).toBe(-1) + + const sceneScript = generateSceneScript(s.scene) + expect(sceneScript).toContain('RUWIO scene') + expect(sceneScript).toContain('ruwio-runtime.js') + + const bundled = generateBundledScript(s.scene) + expect(bundled).toContain('RUWIO bundled export') + expect(bundled.indexOf('/* Animatr ')).toBe(-1) + }) + + test('generated example HTML is RUWIO-branded', () => { + // The example HTML is what users see when they drop the file + // into a browser. Title, heading, and the embed-usage snippet + // all need to carry the new brand. + const s = makeFreshStore() + const html = generateExampleHtml(s.scene) + expect(html).toContain('RUWIO — Example') + expect(html).toContain('

RUWIO Export

') + // The usage snippet now references ruwio-runtime.js. + expect(html).toContain('ruwio-runtime.js') + expect(html.indexOf('animatr-runtime.js')).toBe(-1) + }) }) suite('playback — transport', () => { @@ -818,6 +853,34 @@ export function registerAllTests(): void { } expect(caught instanceof ProjectFileError).toBe(true) }) + + test('legacy `animatr-project` magic still parses (backward compat)', () => { + // When we rebranded to RUWIO we kept the on-disk magic as + // `animatr-project` so older exports continue to load. Pin that + // so a future cleanup doesn't accidentally break existing user + // files on disk. + const s = makeFreshStore() + s.addAsset({ kind: 'rect', name: 'Legacy', width: 10, height: 10, fill: '#123' }) + const legacyPayload = JSON.parse(serializeProject(s.scene)) + expect(legacyPayload.magic).toBe('animatr-project') + // And a fully-formed legacy file parses without error. + const scene = parseProject(JSON.stringify(legacyPayload)) + expect(scene.assets.some((a) => a.name === 'Legacy')).toBe(true) + }) + + test('parseProject error mentions RUWIO, not Animatr', () => { + // The user-visible error when a non-project JSON is dropped in + // reads "Not a RUWIO project file …" now. If you rebrand again + // remember to update this test alongside the string. + let msg = '' + try { + parseProject(JSON.stringify({ not: 'a project' })) + } catch (e) { + msg = (e as Error).message + } + expect(msg).toContain('RUWIO') + expect(msg.toLowerCase().includes('animatr')).toBe(false) + }) }) suite('history — undo/redo', () => { diff --git a/src/lib/exportPlayer.ts b/src/lib/exportPlayer.ts index fc890fa..08a6e3b 100644 --- a/src/lib/exportPlayer.ts +++ b/src/lib/exportPlayer.ts @@ -1,6 +1,6 @@ import type { Scene } from '@/types/scene' -const RUNTIME_VERSION = '14' +const RUNTIME_VERSION = '15' const ENGINE_SOURCE = `(function(){ if (window.AnimatrPlayer && window.AnimatrPlayer.__v === '${RUNTIME_VERSION}') return; @@ -194,7 +194,7 @@ const ENGINE_SOURCE = `(function(){ forcePlay: false, buttonStates: {}, buttonDisabled: {}, - warn: function(m){ if(window.console)window.console.warn('[animatr]',m); } + warn: function(m){ if(window.console)window.console.warn('[ruwio]',m); } }; (scene.markers||[]).forEach(function(mk){ runtime.markers[mk.name]=mk.time; }); @@ -897,7 +897,7 @@ export interface ExportOptions { export function generateRuntimeScript(): string { return ( - '/* Animatr runtime v' + RUNTIME_VERSION + ' — generated ' + new Date().toISOString() + ' */\n' + + '/* RUWIO runtime v' + RUNTIME_VERSION + ' — generated ' + new Date().toISOString() + ' */\n' + ENGINE_SOURCE ) } @@ -906,8 +906,8 @@ export function generateSceneScript(scene: Scene, options: ExportOptions = {}): const sceneJson = jsonForExport(scene) const optsJson = JSON.stringify({ loop: options.loop !== false }) return ( - '/* Animatr scene — generated ' + new Date().toISOString() + '\n' + - ' * Requires animatr-runtime.js (v' + RUNTIME_VERSION + ') to be loaded once per page.\n' + + '/* RUWIO scene — generated ' + new Date().toISOString() + '\n' + + ' * Requires ruwio-runtime.js (v' + RUNTIME_VERSION + ') to be loaded once per page.\n' + ' */\n' + SCENE_BOOT_SOURCE.replace('__SCENE__', sceneJson).replace('__OPTIONS__', optsJson) ) @@ -915,7 +915,7 @@ export function generateSceneScript(scene: Scene, options: ExportOptions = {}): export function generateBundledScript(scene: Scene, options: ExportOptions = {}): string { return ( - '/* Animatr bundled export — generated ' + new Date().toISOString() + ' */\n' + + '/* RUWIO bundled export — generated ' + new Date().toISOString() + ' */\n' + ENGINE_SOURCE + '\n' + generateSceneScriptBody(scene, options) @@ -928,7 +928,7 @@ function generateSceneScriptBody(scene: Scene, options: ExportOptions): string { return SCENE_BOOT_SOURCE.replace('__SCENE__', sceneJson).replace('__OPTIONS__', optsJson) } -export function downloadRuntime(filename = 'animatr-runtime.js') { +export function downloadRuntime(filename = 'ruwio-runtime.js') { triggerDownload(generateRuntimeScript(), filename, 'application/javascript') } @@ -949,7 +949,7 @@ export function generateExampleHtml(scene: Scene, options: ExportOptions = {}): -Animatr — Example +RUWIO — Example -

Animatr Export

+

RUWIO Export

Load the runtime once, then embed as many scenes on the page as you like.

@@ -1021,7 +1021,7 @@ ${sceneBoot}

Embed on your own page

<!-- once per page -->
-<script src="animatr-runtime.js"></script>
+<script src="ruwio-runtime.js"></script>
 
 <!-- per animation: one container + scene script -->
 <div style="width: 800px; height: 450px;">
diff --git a/src/lib/projectFile.ts b/src/lib/projectFile.ts
index 68da8b5..7ee1066 100644
--- a/src/lib/projectFile.ts
+++ b/src/lib/projectFile.ts
@@ -1,6 +1,8 @@
 import type { Scene } from '@/types/scene'
 
 const FILE_VERSION = 1
+// The on-disk magic stays `animatr-project` so older exports keep loading.
+// Only user-visible filenames & strings have rebranded to RUWIO.
 const MAGIC = 'animatr-project'
 
 export interface ProjectFile {
@@ -39,7 +41,7 @@ export function parseProject(text: string): Scene {
   }
   const obj = data as Record
   if (obj.magic !== MAGIC) {
-    throw new ProjectFileError('Not an Animatr project file (magic missing).')
+    throw new ProjectFileError('Not a RUWIO project file (magic missing).')
   }
   const scene = obj.scene
   if (!scene || typeof scene !== 'object') {
@@ -58,7 +60,7 @@ export function parseProject(text: string): Scene {
   return scene as Scene
 }
 
-export function downloadProject(scene: Scene, filename = 'projekt.animatr.json') {
+export function downloadProject(scene: Scene, filename = 'projekt.ruwio.json') {
   const source = serializeProject(scene)
   const blob = new Blob([source], { type: 'application/json' })
   const url = URL.createObjectURL(blob)
diff --git a/src/stores/scene.ts b/src/stores/scene.ts
index 05c4325..4793f9c 100644
--- a/src/stores/scene.ts
+++ b/src/stores/scene.ts
@@ -1760,7 +1760,7 @@ export const useSceneStore = defineStore('scene', {
         },
         warn(msg: string) {
           // eslint-disable-next-line no-console
-          console.warn('[animatr]', msg)
+          console.warn('[ruwio]', msg)
         },
       }
     },
diff --git a/src/version.ts b/src/version.ts
index 4893063..004d6d9 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1,2 +1,2 @@
-export const APP_VERSION = '0.8.1'
+export const APP_VERSION = '0.8.2'
 export const APP_NAME = 'RUWIO'
diff --git a/todo b/todo
index edb76ef..6ba40ea 100644
--- a/todo
+++ b/todo
@@ -144,4 +144,6 @@ warum ist eigentlich still frame easing disabled? man muss doch zu einem still-f
 
  ich würde an dieser stelle mal ein paar regression-tests hinzufügen und dann committen
 
-wenn man den leeren teil des content-views zieht (außerhalb der canvas constraints) muss canvas move auch ohne control key gehen
\ No newline at end of file
+wenn man den leeren teil des content-views zieht (außerhalb der canvas constraints) muss canvas move auch ohne control key gehen
+
+export dateien heißen noch animatr. wir haben in RUWIO umbenannt
\ No newline at end of file