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