refactor(vishot-*,scenarios-*): better structure, better api, and markup components
@@ -315,6 +315,7 @@ words:
|
||||
- Unlisten
|
||||
- unocss
|
||||
- unplugin
|
||||
- unref
|
||||
- unspeech
|
||||
- upterm
|
||||
- userversion
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 42 KiB |
@@ -8,43 +8,67 @@ This package is the tamagotchi browser composition layer. It contains:
|
||||
|
||||
- the Vite/Vue scene app
|
||||
- the scene composition components and capture roots
|
||||
- file-based scene routing from `src/scenes/**` via `unplugin-vue-router`
|
||||
- the `capture` script that exports final browser captures through `@proj-airi/vishot-runner-browser`
|
||||
|
||||
It expects raw business screenshots to exist in `artifacts/raw` before final export runs.
|
||||
|
||||
Current docs capture target route:
|
||||
|
||||
- `/docs/setup-and-use` -> `src/scenes/docs/setup-and-use/index.vue`
|
||||
- `/docs/setup-and-use/intro-chat` -> `src/scenes/docs/setup-and-use/intro-chat.vue`
|
||||
- `/docs/setup-and-use/intro-websocket` -> `src/scenes/docs/setup-and-use/intro-websocket.vue`
|
||||
- `/docs/setup-and-use/main-window` -> `src/scenes/docs/setup-and-use/main-window.vue`
|
||||
- `/docs/setup-and-use/settings` -> `src/scenes/docs/setup-and-use/settings.vue`
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Build the Electron app.
|
||||
2. Capture raw screenshots into this package's `artifacts/raw` directory.
|
||||
3. Run this package's `capture` script to export the composed scene roots into `artifacts/final`.
|
||||
1. Run Electron capture first and populate `artifacts/raw` (see electron package README).
|
||||
2. Run browser capture and output final composed files to docs assets.
|
||||
|
||||
## Agent Quickstart
|
||||
|
||||
From repo root, run:
|
||||
|
||||
```bash
|
||||
pnpm -F @proj-airi/stage-tamagotchi build
|
||||
pnpm -F @proj-airi/vishot-runner-electron capture -- ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket.ts --output-dir ../../packages/scenarios-stage-tamagotchi-browser/artifacts/raw
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture
|
||||
pnpm -F @proj-airi/vishot-runner-electron capture ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket/index.ts --output-dir ../../packages/scenarios-stage-tamagotchi-browser/artifacts/raw --format avif
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/intro-chat' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/intro-websocket' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/main-window' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route '/docs/setup-and-use/settings' --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800
|
||||
```
|
||||
|
||||
Expected raw inputs:
|
||||
Expected results:
|
||||
|
||||
- `artifacts/raw/00-stage-tamagotchi.png`
|
||||
- `artifacts/raw/04-websocket-settings.png`
|
||||
- Raw: `27` files under `packages/scenarios-stage-tamagotchi-browser/artifacts/raw`
|
||||
- Final docs assets: `manual-*.avif` and `intro-chat-window.avif` under `docs/content/en/docs/manual/tamagotchi/setup-and-use/assets`
|
||||
|
||||
Current composed outputs:
|
||||
## Capture Flags
|
||||
|
||||
- `artifacts/final/intro-chat-window.png`
|
||||
- `artifacts/final/intro-websocket-settings.png`
|
||||
The browser capture script supports:
|
||||
|
||||
Optional AVIF export:
|
||||
- `--format <png|avif>` (default: `png`)
|
||||
- `--route </path>` (default: `/docs/setup-and-use`)
|
||||
- `--output-dir <path>` (default: `artifacts/final`)
|
||||
- `--settle-ms <number>` (default: `500`) extra delay after scene ready before screenshot
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture -- --format avif
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --settle-ms 800
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --route /docs/setup-and-use --settle-ms 800
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --route /docs/setup-and-use/settings --settle-ms 800
|
||||
pnpm -F @proj-airi/scenarios-stage-tamagotchi-browser capture --format avif --route /docs/setup-and-use --output-dir ../../docs/content/en/docs/manual/tamagotchi/setup-and-use/assets --settle-ms 800
|
||||
```
|
||||
|
||||
The browser capture flow still starts from PNG because that is the Playwright screenshot format. The package's `capture` script can opt into an AVIF post-processing step through Vishot's `imageTransformers` hook.
|
||||
The browser capture flow starts from Playwright PNG screenshots, then optionally transforms to AVIF via Vishot `imageTransformers`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The current scene app renders `src/scenes/intro-manual-scene.vue`.
|
||||
- This package can host multiple scene pages. Add Vue SFC routes under `src/scenes/**` (for example `src/scenes/docs/setup-and-use/index.vue`).
|
||||
- The default capture route is `/docs/setup-and-use`. Override it with `--route /your/path`.
|
||||
- Captures are produced only for roots mounted by the exact route you pass. For full setup manual assets, run captures for `/docs/setup-and-use/intro-chat`, `/docs/setup-and-use/intro-websocket`, `/docs/setup-and-use/main-window`, and `/docs/setup-and-use/settings`.
|
||||
- Final export depends on the browser scene reaching the `__SCENARIO_CAPTURE_READY__` flag after its raw images load.
|
||||
- If raw capture is missing or stale, final export will fail or render outdated assets.
|
||||
- If you enable AVIF output, the final artifact filenames switch from `.png` to `.avif` because the transformer replaces the emitted PNG files after capture.
|
||||
- Paths in these `pnpm -F` examples are resolved from the filtered package working directory.
|
||||
|
||||
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -26,15 +26,20 @@
|
||||
"@napi-rs/image": "^1.12.0",
|
||||
"@proj-airi/vishot-runner-browser": "workspace:^",
|
||||
"@proj-airi/vishot-runtime": "workspace:^",
|
||||
"@vueuse/core": "catalog:",
|
||||
"date-fns": "catalog:",
|
||||
"vue": "catalog:"
|
||||
"reka-ui": "^2.9.5",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/line-md": "catalog:",
|
||||
"@iconify-json/material-symbols": "catalog:",
|
||||
"@iconify-json/mdi": "catalog:",
|
||||
"@iconify-json/ph": "catalog:",
|
||||
"@unocss/reset": "^66.6.8",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
"unplugin-vue-router": "^0.19.2",
|
||||
"vue-tsc": "^3.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,42 @@ import { argv, cwd } from 'node:process'
|
||||
import { Transformer } from '@napi-rs/image'
|
||||
import { captureBrowserRoots } from '@proj-airi/vishot-runner-browser'
|
||||
|
||||
const DEFAULT_AVIF_MAX_WIDTH = 1200
|
||||
const DEFAULT_AVIF_QUALITY = 35
|
||||
const DEFAULT_AVIF_SPEED = 6
|
||||
|
||||
const sceneAppRoot = path.resolve(cwd())
|
||||
const outputDir = path.resolve(sceneAppRoot, 'artifacts', 'final')
|
||||
const formatFlagIndex = argv.findIndex(arg => arg === '--format')
|
||||
const routeFlagIndex = argv.findIndex(arg => arg === '--route')
|
||||
const outputDirFlagIndex = argv.findIndex(arg => arg === '--output-dir')
|
||||
const settleMsFlagIndex = argv.findIndex(arg => arg === '--settle-ms')
|
||||
const avifMaxWidthFlagIndex = argv.findIndex(arg => arg === '--avif-max-width')
|
||||
const avifQualityFlagIndex = argv.findIndex(arg => arg === '--avif-quality')
|
||||
const avifSpeedFlagIndex = argv.findIndex(arg => arg === '--avif-speed')
|
||||
const requestedFormat = formatFlagIndex >= 0 ? argv[formatFlagIndex + 1] : 'png'
|
||||
const routePath = routeFlagIndex >= 0 ? argv[routeFlagIndex + 1] : '/docs/setup-and-use'
|
||||
const settleMs = settleMsFlagIndex >= 0 ? Number(argv[settleMsFlagIndex + 1]) : 500
|
||||
const avifMaxWidth = avifMaxWidthFlagIndex >= 0 ? Number(argv[avifMaxWidthFlagIndex + 1]) : DEFAULT_AVIF_MAX_WIDTH
|
||||
const avifQuality = avifQualityFlagIndex >= 0 ? Number(argv[avifQualityFlagIndex + 1]) : DEFAULT_AVIF_QUALITY
|
||||
const avifSpeed = avifSpeedFlagIndex >= 0 ? Number(argv[avifSpeedFlagIndex + 1]) : DEFAULT_AVIF_SPEED
|
||||
const outputDir = outputDirFlagIndex >= 0
|
||||
? path.resolve(sceneAppRoot, argv[outputDirFlagIndex + 1])
|
||||
: path.resolve(sceneAppRoot, 'artifacts', 'final')
|
||||
|
||||
const avifTransformer: ArtifactTransformer = async (artifact) => {
|
||||
const derivedFilePath = artifact.filePath.replace(/\.png$/i, '.avif')
|
||||
const avifBuffer = await new Transformer(await readFile(artifact.filePath)).avif()
|
||||
const transformer = new Transformer(await readFile(artifact.filePath))
|
||||
const metadata = await transformer.metadata()
|
||||
|
||||
if (metadata.width > avifMaxWidth) {
|
||||
const scale = avifMaxWidth / metadata.width
|
||||
transformer.resize(avifMaxWidth, Math.round(metadata.height * scale))
|
||||
}
|
||||
|
||||
const avifBuffer = await transformer.avif({
|
||||
quality: avifQuality,
|
||||
speed: avifSpeed,
|
||||
})
|
||||
|
||||
await writeFile(derivedFilePath, avifBuffer)
|
||||
await rm(artifact.filePath, { force: true })
|
||||
@@ -31,13 +59,54 @@ if (!['png', 'avif'].includes(requestedFormat)) {
|
||||
throw new Error(`Unsupported capture format "${requestedFormat}". Expected "png" or "avif".`)
|
||||
}
|
||||
|
||||
if (!routePath?.startsWith('/')) {
|
||||
throw new Error(`Unsupported route path "${routePath}". Route paths must start with "/".`)
|
||||
}
|
||||
|
||||
if (outputDirFlagIndex >= 0 && !argv[outputDirFlagIndex + 1]) {
|
||||
throw new Error('Missing value for --output-dir.')
|
||||
}
|
||||
|
||||
if (settleMsFlagIndex >= 0 && !argv[settleMsFlagIndex + 1]) {
|
||||
throw new Error('Missing value for --settle-ms.')
|
||||
}
|
||||
|
||||
if (avifMaxWidthFlagIndex >= 0 && !argv[avifMaxWidthFlagIndex + 1]) {
|
||||
throw new Error('Missing value for --avif-max-width.')
|
||||
}
|
||||
|
||||
if (avifQualityFlagIndex >= 0 && !argv[avifQualityFlagIndex + 1]) {
|
||||
throw new Error('Missing value for --avif-quality.')
|
||||
}
|
||||
|
||||
if (avifSpeedFlagIndex >= 0 && !argv[avifSpeedFlagIndex + 1]) {
|
||||
throw new Error('Missing value for --avif-speed.')
|
||||
}
|
||||
|
||||
if (!Number.isFinite(settleMs) || settleMs < 0) {
|
||||
throw new Error(`Unsupported settle delay "${argv[settleMsFlagIndex + 1] ?? settleMs}". Expected a number >= 0.`)
|
||||
}
|
||||
|
||||
if (!Number.isFinite(avifMaxWidth) || avifMaxWidth < 1) {
|
||||
throw new Error(`Unsupported AVIF max width "${argv[avifMaxWidthFlagIndex + 1] ?? avifMaxWidth}". Expected a number >= 1.`)
|
||||
}
|
||||
|
||||
if (!Number.isFinite(avifQuality) || avifQuality < 0 || avifQuality > 100) {
|
||||
throw new Error(`Unsupported AVIF quality "${argv[avifQualityFlagIndex + 1] ?? avifQuality}". Expected a number between 0 and 100.`)
|
||||
}
|
||||
|
||||
if (!Number.isFinite(avifSpeed) || avifSpeed < 1 || avifSpeed > 10) {
|
||||
throw new Error(`Unsupported AVIF speed "${argv[avifSpeedFlagIndex + 1] ?? avifSpeed}". Expected a number between 1 and 10.`)
|
||||
}
|
||||
|
||||
await captureBrowserRoots({
|
||||
imageTransformers: requestedFormat === 'avif'
|
||||
? [avifTransformer]
|
||||
: undefined,
|
||||
sceneAppRoot,
|
||||
routePath: '/',
|
||||
routePath,
|
||||
outputDir,
|
||||
settleMs,
|
||||
viewport: {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import IntroManualScene from './scenes/intro-manual-scene.vue'
|
||||
import { ScreenNavigator, ScreenRouterProvider } from '@proj-airi/vishot-runtime/vue'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const sceneNavigatorEnabled = computed(() => route.meta.sceneNavigator !== false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<IntroManualScene />
|
||||
<ScreenRouterProvider>
|
||||
<RouterView />
|
||||
<ScreenNavigator v-if="sceneNavigatorEnabled" />
|
||||
</ScreenRouterProvider>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createApp, h } from 'vue'
|
||||
|
||||
import PlatformRoot from './PlatformRoot.vue'
|
||||
|
||||
vi.mock('./ui', () => ({
|
||||
Appearance: {
|
||||
template: '<div data-test-appearance />',
|
||||
},
|
||||
Dock: {
|
||||
template: '<div data-test-dock><slot name="dock" /></div>',
|
||||
},
|
||||
MenuBar: {
|
||||
template: '<div data-test-menu-bar />',
|
||||
},
|
||||
}))
|
||||
|
||||
describe('platformRoot', () => {
|
||||
it('renders wallpaper behind the windows layer', () => {
|
||||
const host = document.createElement('div')
|
||||
document.body.appendChild(host)
|
||||
|
||||
const app = createApp({
|
||||
render: () => h(PlatformRoot, null, {
|
||||
windows: () => h('div', { 'data-test-window': 'true' }),
|
||||
}),
|
||||
})
|
||||
|
||||
app.mount(host)
|
||||
|
||||
const wallpaper = host.querySelector<HTMLElement>('[data-test-appearance]')
|
||||
const windowNode = host.querySelector<HTMLElement>('[data-test-window="true"]')
|
||||
|
||||
expect(wallpaper).not.toBeNull()
|
||||
expect(windowNode).not.toBeNull()
|
||||
expect(Boolean(wallpaper && windowNode && (wallpaper.compareDocumentPosition(windowNode) & Node.DOCUMENT_POSITION_FOLLOWING))).toBe(true)
|
||||
|
||||
app.unmount()
|
||||
host.remove()
|
||||
})
|
||||
})
|
||||
@@ -9,9 +9,11 @@ import { Appearance, Dock, MenuBar } from './ui'
|
||||
const props = withDefaults(defineProps<{
|
||||
aspectRatio?: string | number
|
||||
dockSize?: number
|
||||
uiScale?: number
|
||||
}>(), {
|
||||
aspectRatio: '16:9',
|
||||
dockSize: 1.5,
|
||||
uiScale: 1,
|
||||
})
|
||||
|
||||
const aspectRatio = computed(() => {
|
||||
@@ -25,18 +27,19 @@ const aspectRatio = computed(() => {
|
||||
return props.aspectRatio.split(':').map(Number).reduce((a, b) => a / b)
|
||||
})
|
||||
|
||||
const platformRoot = ref<HTMLElement | null>(null)
|
||||
const platformSurface = ref<HTMLElement | null>(null)
|
||||
const dockRoot = ref<HTMLElement | null>(null)
|
||||
const normalizedUiScale = computed(() => props.uiScale > 0 ? props.uiScale : 1)
|
||||
|
||||
provide(injectPlatformLayout, {
|
||||
dock: dockRoot,
|
||||
root: readonly(platformRoot) as Readonly<Ref<HTMLElement | null>>,
|
||||
root: readonly(platformSurface) as Readonly<Ref<HTMLElement | null>>,
|
||||
uiScale: normalizedUiScale,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="platformRoot"
|
||||
:class="[
|
||||
'relative overflow-hidden',
|
||||
'font-macos',
|
||||
@@ -46,16 +49,27 @@ provide(injectPlatformLayout, {
|
||||
}"
|
||||
>
|
||||
<div
|
||||
ref="platformSurface"
|
||||
:class="[
|
||||
'relative z-999',
|
||||
'w-full h-full',
|
||||
]"
|
||||
:style="{
|
||||
width: `${100 / normalizedUiScale}%`,
|
||||
height: `${100 / normalizedUiScale}%`,
|
||||
transform: `scale(${normalizedUiScale})`,
|
||||
transformOrigin: 'top left',
|
||||
}"
|
||||
>
|
||||
<Appearance />
|
||||
<slot name="windows" />
|
||||
<MenuBar />
|
||||
<Dock :size="props.dockSize">
|
||||
<template #dock>
|
||||
<slot name="dock" />
|
||||
</template>
|
||||
</Dock>
|
||||
</div>
|
||||
<Appearance />
|
||||
<MenuBar />
|
||||
<Dock :size="props.dockSize" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { InjectionKey, Ref } from 'vue'
|
||||
export interface PlatformLayoutContext {
|
||||
dock: Ref<HTMLElement | null>
|
||||
root: Readonly<Ref<HTMLElement | null>>
|
||||
uiScale: Readonly<Ref<number>>
|
||||
}
|
||||
|
||||
export const injectPlatformLayout: InjectionKey<PlatformLayoutContext> = Symbol('vishot:platforms:macos-26:layout')
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createApp, h, nextTick, readonly, ref } from 'vue'
|
||||
|
||||
import WindowRoot from './WindowRoot.vue'
|
||||
|
||||
import { injectPlatformLayout } from '../../constants'
|
||||
|
||||
function createRect(left: number, top: number, width: number, height: number): DOMRect {
|
||||
return {
|
||||
bottom: top + height,
|
||||
height,
|
||||
left,
|
||||
right: left + width,
|
||||
toJSON: () => ({}),
|
||||
top,
|
||||
width,
|
||||
x: left,
|
||||
y: top,
|
||||
} as DOMRect
|
||||
}
|
||||
|
||||
describe('windowRoot', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('re-anchors to workarea when dock is registered after mount', async () => {
|
||||
vi.stubGlobal('requestAnimationFrame', ((callback: FrameRequestCallback) => {
|
||||
callback(0)
|
||||
return 1
|
||||
}) as typeof requestAnimationFrame)
|
||||
vi.stubGlobal('cancelAnimationFrame', (() => {}) as typeof cancelAnimationFrame)
|
||||
|
||||
const host = document.createElement('div')
|
||||
document.body.appendChild(host)
|
||||
|
||||
const platformRef = ref<HTMLElement | null>(null)
|
||||
const dockRef = ref<HTMLElement | null>(null)
|
||||
const uiScaleRef = ref(1)
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () => h('div', { ref: platformRef }, [
|
||||
h(WindowRoot, {
|
||||
anchorBounds: 'workarea',
|
||||
anchorTo: 'bottom-right',
|
||||
frame: false,
|
||||
hasShadow: false,
|
||||
style: {
|
||||
bottom: '0px',
|
||||
right: '0px',
|
||||
},
|
||||
}, () => h('div')),
|
||||
])
|
||||
},
|
||||
provide: {
|
||||
[injectPlatformLayout as symbol]: {
|
||||
dock: dockRef,
|
||||
root: readonly(platformRef),
|
||||
uiScale: readonly(uiScaleRef),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
app.mount(host)
|
||||
await nextTick()
|
||||
|
||||
const platformEl = platformRef.value
|
||||
const windowEl = host.querySelector<HTMLElement>('.absolute.flex.flex-col.rounded-2xl.overflow-hidden')
|
||||
|
||||
expect(platformEl).not.toBeNull()
|
||||
expect(windowEl).not.toBeNull()
|
||||
|
||||
vi.spyOn(platformEl!, 'getBoundingClientRect').mockReturnValue(createRect(0, 0, 1000, 600))
|
||||
vi.spyOn(windowEl!, 'getBoundingClientRect').mockReturnValue(createRect(900, 500, 100, 100))
|
||||
|
||||
const dockEl = document.createElement('div')
|
||||
vi.spyOn(dockEl, 'getBoundingClientRect').mockReturnValue(createRect(920, 200, 70, 200))
|
||||
dockRef.value = dockEl
|
||||
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
expect(windowEl!.style.right).toBe('80px')
|
||||
|
||||
app.unmount()
|
||||
host.remove()
|
||||
})
|
||||
|
||||
it('uses measured workarea bounds when uiScale is applied so dock does not overlap anchored window', async () => {
|
||||
vi.stubGlobal('requestAnimationFrame', ((callback: FrameRequestCallback) => {
|
||||
callback(0)
|
||||
return 1
|
||||
}) as typeof requestAnimationFrame)
|
||||
vi.stubGlobal('cancelAnimationFrame', (() => {}) as typeof cancelAnimationFrame)
|
||||
|
||||
const host = document.createElement('div')
|
||||
document.body.appendChild(host)
|
||||
|
||||
const platformRef = ref<HTMLElement | null>(null)
|
||||
const dockRef = ref<HTMLElement | null>(null)
|
||||
const uiScaleRef = ref(1.25)
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () => h('div', { ref: platformRef }, [
|
||||
h(WindowRoot, {
|
||||
anchorBounds: 'workarea',
|
||||
anchorTo: 'bottom-right',
|
||||
frame: false,
|
||||
hasShadow: false,
|
||||
style: {
|
||||
bottom: '0px',
|
||||
right: '0px',
|
||||
},
|
||||
}, () => h('div')),
|
||||
])
|
||||
},
|
||||
provide: {
|
||||
[injectPlatformLayout as symbol]: {
|
||||
dock: dockRef,
|
||||
root: readonly(platformRef),
|
||||
uiScale: readonly(uiScaleRef),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
app.mount(host)
|
||||
await nextTick()
|
||||
|
||||
const platformEl = platformRef.value
|
||||
const windowEl = host.querySelector<HTMLElement>('.absolute.flex.flex-col.rounded-2xl.overflow-hidden')
|
||||
|
||||
expect(platformEl).not.toBeNull()
|
||||
expect(windowEl).not.toBeNull()
|
||||
|
||||
vi.spyOn(platformEl!, 'getBoundingClientRect').mockReturnValue(createRect(0, 0, 1646, 926))
|
||||
vi.spyOn(windowEl!, 'getBoundingClientRect').mockReturnValue(createRect(1239, 383, 407, 543))
|
||||
|
||||
const dockEl = document.createElement('div')
|
||||
vi.spyOn(dockEl, 'getBoundingClientRect').mockReturnValue(createRect(1571, 323, 66, 280))
|
||||
dockRef.value = dockEl
|
||||
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
expect(windowEl!.style.right).toBe('75px')
|
||||
|
||||
app.unmount()
|
||||
host.remove()
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type { CSSProperties, StyleValue } from 'vue'
|
||||
|
||||
import { inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, useAttrs, watch } from 'vue'
|
||||
|
||||
import { injectPlatformLayout } from '../../constants'
|
||||
import { computeElementAnchorStyle, createContainerAnchorStyle, createWorkAreaRect } from './window-anchor'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
title?: string
|
||||
focus?: boolean
|
||||
@@ -26,8 +30,14 @@ const props = withDefaults(defineProps<{
|
||||
})
|
||||
|
||||
const platformLayout = inject(injectPlatformLayout, null)
|
||||
const attrs = useAttrs()
|
||||
const windowRoot = ref<HTMLElement | null>(null)
|
||||
const anchorStyle = shallowRef<CSSProperties | undefined>()
|
||||
const incomingStyle = computed<StyleValue | undefined>(() => attrs.style as StyleValue | undefined)
|
||||
const forwardedAttrs = computed(() => {
|
||||
const { style: _style, ...rest } = attrs
|
||||
return rest
|
||||
})
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
let animationFrameId: number | null = null
|
||||
@@ -66,9 +76,13 @@ function updateAnchorStyle() {
|
||||
const platformRect = currentPlatformRoot?.getBoundingClientRect()
|
||||
// When anchoring against the container, we can optionally shrink the usable
|
||||
// bounds to the platform work area so windows avoid overlapping the dock.
|
||||
const dockRect = platformLayout?.dock.value
|
||||
? platformLayout.dock.value.getBoundingClientRect()
|
||||
: null
|
||||
|
||||
const workAreaRect = platformRect
|
||||
? createWorkAreaRect({
|
||||
dockRect: props.anchorBounds === 'workarea' ? platformLayout?.dock.value?.getBoundingClientRect() ?? null : null,
|
||||
dockRect: props.anchorBounds === 'workarea' ? dockRect : null,
|
||||
platformRect,
|
||||
})
|
||||
: undefined
|
||||
@@ -131,7 +145,14 @@ onMounted(async () => {
|
||||
refreshResizeObserver()
|
||||
})
|
||||
|
||||
watch(() => [props.anchorBounds, props.anchorTo, props.anchorEl], async () => {
|
||||
watch(() => [
|
||||
props.anchorBounds,
|
||||
props.anchorTo,
|
||||
props.anchorEl,
|
||||
platformLayout?.uiScale.value,
|
||||
platformLayout?.dock.value,
|
||||
platformLayout?.root.value,
|
||||
], async () => {
|
||||
await nextTick()
|
||||
queueAnchorUpdate()
|
||||
refreshResizeObserver()
|
||||
@@ -146,13 +167,14 @@ onBeforeUnmount(() => {
|
||||
<template>
|
||||
<div
|
||||
ref="windowRoot"
|
||||
v-bind="forwardedAttrs"
|
||||
:class="[
|
||||
'absolute',
|
||||
'flex flex-col',
|
||||
'rounded-2xl overflow-hidden',
|
||||
props.hasShadow ? 'shadow-xl' : '',
|
||||
]"
|
||||
:style="anchorStyle"
|
||||
:style="[incomingStyle, anchorStyle]"
|
||||
>
|
||||
<div
|
||||
v-if="!!props.frame"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { computeElementAnchorStyle, createContainerAnchorStyle, createWorkAreaRect } from './window-anchor'
|
||||
import { computeElementAnchorStyle, createContainerAnchorStyle, createWorkAreaRect, normalizeRectForScale } from './window-anchor'
|
||||
|
||||
describe('createContainerAnchorStyle', () => {
|
||||
it('anchors a window to the platform top-right corner', () => {
|
||||
@@ -82,3 +82,19 @@ describe('computeElementAnchorStyle', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeRectForScale', () => {
|
||||
it('converts measured rect values into logical coordinates for scaled platform UI', () => {
|
||||
expect(normalizeRectForScale({
|
||||
left: 960,
|
||||
top: 240,
|
||||
width: 640,
|
||||
height: 360,
|
||||
}, 2)).toEqual({
|
||||
left: 480,
|
||||
top: 120,
|
||||
width: 320,
|
||||
height: 180,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,6 +7,20 @@ type RectSize = Pick<DOMRect, 'width' | 'height'>
|
||||
type RectLike = RectPosition & RectSize
|
||||
type RelativeRect = RectLike
|
||||
|
||||
export function normalizeRectForScale<T extends RectLike>(rect: T, uiScale: number): T {
|
||||
if (uiScale <= 0 || uiScale === 1) {
|
||||
return rect
|
||||
}
|
||||
|
||||
return {
|
||||
...rect,
|
||||
left: rect.left / uiScale,
|
||||
top: rect.top / uiScale,
|
||||
width: rect.width / uiScale,
|
||||
height: rect.height / uiScale,
|
||||
}
|
||||
}
|
||||
|
||||
function toRelativeRect(rect: RectLike, platformRect: RectPosition): RelativeRect {
|
||||
// All anchor math is done in the platform's own coordinate system so callers
|
||||
// can mix measured DOM rects with the logical scene layout consistently.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createApp, h } from 'vue'
|
||||
|
||||
import Appearance from './appearance.vue'
|
||||
|
||||
describe('appearance', () => {
|
||||
it('does not pin wallpaper above window content via z-index utility classes', () => {
|
||||
const host = document.createElement('div')
|
||||
document.body.appendChild(host)
|
||||
|
||||
const app = createApp({
|
||||
render: () => h(Appearance),
|
||||
})
|
||||
|
||||
app.mount(host)
|
||||
|
||||
const wallpaper = host.querySelector<HTMLImageElement>('img')
|
||||
expect(wallpaper).not.toBeNull()
|
||||
expect(wallpaper?.className.includes('z-1')).toBe(false)
|
||||
|
||||
app.unmount()
|
||||
host.remove()
|
||||
})
|
||||
})
|
||||
@@ -6,7 +6,6 @@ import wallpaper from '../../../../../assets/wallpaper-2025-08-12.jpg'
|
||||
<img
|
||||
:class="[
|
||||
'absolute left-0 top-0 h-full w-full',
|
||||
'z-1',
|
||||
]"
|
||||
:src="wallpaper"
|
||||
>
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { routes } from 'vue-router/auto-routes'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
import '@unocss/reset/tailwind.css'
|
||||
import 'uno.css'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
const router = createRouter({
|
||||
routes,
|
||||
history: createWebHistory(),
|
||||
})
|
||||
|
||||
createApp(App)
|
||||
.use(router)
|
||||
.mount('#app')
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { ScenarioCanvas, ScreenMarupsProvider, ScreenRouterCaptureRoot } from '@proj-airi/vishot-runtime/vue'
|
||||
|
||||
import Icon from '../../../../components/icon.vue'
|
||||
|
||||
import { PlatformRoot } from '../../../../components/platforms/macos-26'
|
||||
import { Application } from '../../../../components/platforms/macos-26/containers/dock'
|
||||
|
||||
interface SceneCaptureLayoutProps {
|
||||
height?: number
|
||||
name: string
|
||||
platformUiScale?: number
|
||||
scaleMultiplier?: number
|
||||
width?: number
|
||||
}
|
||||
|
||||
withDefaults(defineProps<SceneCaptureLayoutProps>(), {
|
||||
height: 1080,
|
||||
platformUiScale: 1,
|
||||
scaleMultiplier: 1,
|
||||
width: 1920,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScreenRouterCaptureRoot :name="name">
|
||||
<ScenarioCanvas :width="width" :height="height" :scale-multiplier="scaleMultiplier">
|
||||
<ScreenMarupsProvider :width="width" :height="height">
|
||||
<PlatformRoot :dock-size="1.5" :ui-scale="platformUiScale">
|
||||
<template #windows>
|
||||
<slot name="windows" />
|
||||
</template>
|
||||
<template #dock>
|
||||
<Application running>
|
||||
<Icon />
|
||||
</Application>
|
||||
</template>
|
||||
</PlatformRoot>
|
||||
|
||||
<template #overlay>
|
||||
<slot name="overlay" />
|
||||
</template>
|
||||
</ScreenMarupsProvider>
|
||||
</ScenarioCanvas>
|
||||
</ScreenRouterCaptureRoot>
|
||||
</template>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { useSceneReady } from '@proj-airi/vishot-runtime/vue'
|
||||
|
||||
import stageShot from '../../../../artifacts/raw/00-stage-tamagotchi.avif'
|
||||
import websocketSettingsShot from '../../../../artifacts/raw/03-websocket-settings.avif'
|
||||
import chatWindowShot from '../../../../artifacts/raw/04-chat-window.avif'
|
||||
import SceneCaptureLayout from './components/scene-capture-layout.vue'
|
||||
|
||||
import { WindowRoot } from '../../../components/platforms/macos-26/containers/window'
|
||||
|
||||
useSceneReady([
|
||||
stageShot,
|
||||
websocketSettingsShot,
|
||||
chatWindowShot,
|
||||
])
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
title: Setup And Use
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<SceneCaptureLayout name="manual-chat-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '72px', transform: 'translateX(-50%)' }">
|
||||
<img :src="chatWindowShot" :class="['w-150']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="intro-chat-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="{ right: '0px', bottom: '0px' }"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="stageShot" :class="['w-95']">
|
||||
</WindowRoot>
|
||||
|
||||
<WindowRoot :style="{ left: '480px', top: '120px' }">
|
||||
<img :src="websocketSettingsShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { useSceneReady } from '@proj-airi/vishot-runtime/vue'
|
||||
|
||||
import stageShot from '../../../../artifacts/raw/00-stage-tamagotchi.avif'
|
||||
import websocketSettingsShot from '../../../../artifacts/raw/03-websocket-settings.avif'
|
||||
import chatWindowShot from '../../../../artifacts/raw/04-chat-window.avif'
|
||||
import SceneCaptureLayout from './components/scene-capture-layout.vue'
|
||||
|
||||
import { WindowRoot } from '../../../components/platforms/macos-26/containers/window'
|
||||
|
||||
useSceneReady([
|
||||
stageShot,
|
||||
websocketSettingsShot,
|
||||
chatWindowShot,
|
||||
])
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
title: Setup And Use Intro Chat
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<SceneCaptureLayout name="manual-chat-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '72px', transform: 'translateX(-50%)' }">
|
||||
<img :src="chatWindowShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="intro-chat-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="{ right: '0px', bottom: '0px' }"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="stageShot" :class="['w-95']">
|
||||
</WindowRoot>
|
||||
|
||||
<WindowRoot :style="{ left: '480px', top: '120px' }">
|
||||
<img :src="websocketSettingsShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { ScreenHighlightRegion, useSceneReady } from '@proj-airi/vishot-runtime/vue'
|
||||
|
||||
import stageShot from '../../../../artifacts/raw/00-stage-tamagotchi.avif'
|
||||
import websocketSettingsShot from '../../../../artifacts/raw/03-websocket-settings.avif'
|
||||
import SceneCaptureLayout from './components/scene-capture-layout.vue'
|
||||
|
||||
import { WindowRoot } from '../../../components/platforms/macos-26/containers/window'
|
||||
|
||||
useSceneReady([
|
||||
stageShot,
|
||||
websocketSettingsShot,
|
||||
])
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
title: Setup And Use Intro Websocket
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<SceneCaptureLayout name="intro-chat-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="{ right: '0px', bottom: '0px' }"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="stageShot" :class="['w-95']">
|
||||
</WindowRoot>
|
||||
|
||||
<WindowRoot :style="{ left: '480px', top: '120px' }">
|
||||
<div :class="['relative']">
|
||||
<img :src="websocketSettingsShot" :class="['w-120']">
|
||||
<div
|
||||
:class="[
|
||||
'absolute inset-0',
|
||||
'pointer-events-none',
|
||||
]"
|
||||
>
|
||||
<ScreenHighlightRegion
|
||||
:class="[
|
||||
'absolute left-2.5 top-22',
|
||||
'w-[calc(100%-(16px*2))] h-20',
|
||||
'translate-x-[8px]',
|
||||
]"
|
||||
:inset="3"
|
||||
border-color="border-yellow-500 dark:border-yellow-400"
|
||||
/>
|
||||
<ScreenHighlightRegion
|
||||
:class="[
|
||||
'absolute left-2.5 top-78',
|
||||
'w-[calc(100%-(16px*2))] h-26',
|
||||
'translate-x-[8px]',
|
||||
]"
|
||||
:inset="3"
|
||||
border-color="border-cyan-400 dark:border-cyan-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { useSceneReady } from '@proj-airi/vishot-runtime/vue'
|
||||
|
||||
import stageShot from '../../../../artifacts/raw/00-stage-tamagotchi.avif'
|
||||
import controlsIslandShot from '../../../../artifacts/raw/01-controls-island-expanded.avif'
|
||||
import SceneCaptureLayout from './components/scene-capture-layout.vue'
|
||||
|
||||
import { WindowRoot } from '../../../components/platforms/macos-26/containers/window'
|
||||
|
||||
useSceneReady([
|
||||
stageShot,
|
||||
controlsIslandShot,
|
||||
])
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
title: Setup And Use Main Window
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<SceneCaptureLayout name="manual-main-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="{ right: '0px', bottom: '0px' }"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="stageShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-controls-island-expanded" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="{ right: '0px', bottom: '0px' }"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="controlsIslandShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
</template>
|
||||
@@ -0,0 +1,257 @@
|
||||
<script setup lang="ts">
|
||||
import { useSceneReady } from '@proj-airi/vishot-runtime/vue'
|
||||
|
||||
import settingsShot from '../../../../artifacts/raw/02-settings-window.avif'
|
||||
import websocketSettingsShot from '../../../../artifacts/raw/03-websocket-settings.avif'
|
||||
import airiCardShot from '../../../../artifacts/raw/05-airi-card.avif'
|
||||
import providersShot from '../../../../artifacts/raw/06-providers.avif'
|
||||
import dataShot from '../../../../artifacts/raw/07-data.avif'
|
||||
import systemGeneralShot from '../../../../artifacts/raw/08-system-general.avif'
|
||||
import systemColorSchemeShot from '../../../../artifacts/raw/09-system-color-scheme.avif'
|
||||
import modelsShot from '../../../../artifacts/raw/10-models.avif'
|
||||
import modulesShot from '../../../../artifacts/raw/11-modules.avif'
|
||||
import hearingShot from '../../../../artifacts/raw/12-hearing.avif'
|
||||
import systemDeveloperShot from '../../../../artifacts/raw/13-system-developer.avif'
|
||||
import consciousnessShot from '../../../../artifacts/raw/14-consciousness.avif'
|
||||
import speechShot from '../../../../artifacts/raw/15-speech.avif'
|
||||
import visionShot from '../../../../artifacts/raw/16-vision.avif'
|
||||
import useWindowMouseShot from '../../../../artifacts/raw/17-devtools-use-window-mouse.avif'
|
||||
import displaysShot from '../../../../artifacts/raw/18-devtools-displays.avif'
|
||||
import widgetsCallingShot from '../../../../artifacts/raw/19-devtools-widgets-calling.avif'
|
||||
import contextFlowShot from '../../../../artifacts/raw/20-devtools-context-flow.avif'
|
||||
import relativeMouseShot from '../../../../artifacts/raw/21-devtools-relative-mouse.avif'
|
||||
import beatSyncShot from '../../../../artifacts/raw/22-devtools-beat-sync.avif'
|
||||
import websocketInspectorShot from '../../../../artifacts/raw/23-devtools-websocket-inspector.avif'
|
||||
import pluginHostShot from '../../../../artifacts/raw/24-devtools-plugin-host.avif'
|
||||
import screenCaptureShot from '../../../../artifacts/raw/25-devtools-screen-capture.avif'
|
||||
import visionCaptureShot from '../../../../artifacts/raw/26-devtools-vision-capture.avif'
|
||||
import SceneCaptureLayout from './components/scene-capture-layout.vue'
|
||||
|
||||
import { WindowRoot } from '../../../components/platforms/macos-26/containers/window'
|
||||
|
||||
useSceneReady([
|
||||
settingsShot,
|
||||
websocketSettingsShot,
|
||||
airiCardShot,
|
||||
providersShot,
|
||||
dataShot,
|
||||
systemGeneralShot,
|
||||
systemColorSchemeShot,
|
||||
modelsShot,
|
||||
modulesShot,
|
||||
hearingShot,
|
||||
systemDeveloperShot,
|
||||
consciousnessShot,
|
||||
speechShot,
|
||||
visionShot,
|
||||
useWindowMouseShot,
|
||||
displaysShot,
|
||||
widgetsCallingShot,
|
||||
contextFlowShot,
|
||||
relativeMouseShot,
|
||||
beatSyncShot,
|
||||
websocketInspectorShot,
|
||||
pluginHostShot,
|
||||
screenCaptureShot,
|
||||
visionCaptureShot,
|
||||
])
|
||||
</script>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
title: Setup And Use Settings
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<SceneCaptureLayout name="manual-settings-window" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="settingsShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-websocket-settings" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="websocketSettingsShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-airi-card" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="airiCardShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-providers" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="providersShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-data-settings" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="dataShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-system-general" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="systemGeneralShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-system-color-scheme" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="systemColorSchemeShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-models" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="modelsShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-modules" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="modulesShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-hearing" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="hearingShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-system-developer" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="systemDeveloperShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-consciousness" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="consciousnessShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-speech" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="speechShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-vision" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="visionShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-use-window-mouse" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="useWindowMouseShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-displays" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="displaysShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-widgets-calling" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="widgetsCallingShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-context-flow" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="contextFlowShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-relative-mouse" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="relativeMouseShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-beat-sync" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="beatSyncShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-websocket-inspector" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="websocketInspectorShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-plugin-host" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="pluginHostShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-screen-capture" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="screenCaptureShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
|
||||
<SceneCaptureLayout name="manual-devtools-vision-capture" :platform-ui-scale="1.25">
|
||||
<template #windows>
|
||||
<WindowRoot :style="{ left: '50%', top: '84px', transform: 'translateX(-50%)' }">
|
||||
<img :src="visionCaptureShot" :class="['w-120']">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
</SceneCaptureLayout>
|
||||
</template>
|
||||
@@ -1,262 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { markScenarioReady, resetScenarioReady } from '@proj-airi/vishot-runtime'
|
||||
import { ScenarioCanvas, ScenarioCaptureRoot } from '@proj-airi/vishot-runtime/vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import stageShot from '../../artifacts/raw/00-stage-tamagotchi.avif'
|
||||
import controlsIslandShot from '../../artifacts/raw/01-controls-island-expanded.avif'
|
||||
import settingsShot from '../../artifacts/raw/02-settings-window.avif'
|
||||
import websocketSettingsShot from '../../artifacts/raw/03-websocket-settings.avif'
|
||||
import chatWindowShot from '../../artifacts/raw/04-chat-window.avif'
|
||||
import airiCardShot from '../../artifacts/raw/05-airi-card.avif'
|
||||
import providersShot from '../../artifacts/raw/06-providers.avif'
|
||||
import dataShot from '../../artifacts/raw/07-data.avif'
|
||||
import systemGeneralShot from '../../artifacts/raw/08-system-general.avif'
|
||||
import systemColorSchemeShot from '../../artifacts/raw/09-system-color-scheme.avif'
|
||||
import modelsShot from '../../artifacts/raw/10-models.avif'
|
||||
import modulesShot from '../../artifacts/raw/11-modules.avif'
|
||||
import hearingShot from '../../artifacts/raw/12-hearing.avif'
|
||||
import systemDeveloperShot from '../../artifacts/raw/13-system-developer.avif'
|
||||
import consciousnessShot from '../../artifacts/raw/14-consciousness.avif'
|
||||
import speechShot from '../../artifacts/raw/15-speech.avif'
|
||||
import visionShot from '../../artifacts/raw/16-vision.avif'
|
||||
import useWindowMouseShot from '../../artifacts/raw/17-devtools-use-window-mouse.avif'
|
||||
import displaysShot from '../../artifacts/raw/18-devtools-displays.avif'
|
||||
import widgetsCallingShot from '../../artifacts/raw/19-devtools-widgets-calling.avif'
|
||||
import contextFlowShot from '../../artifacts/raw/20-devtools-context-flow.avif'
|
||||
import relativeMouseShot from '../../artifacts/raw/21-devtools-relative-mouse.avif'
|
||||
import beatSyncShot from '../../artifacts/raw/22-devtools-beat-sync.avif'
|
||||
import websocketInspectorShot from '../../artifacts/raw/23-devtools-websocket-inspector.avif'
|
||||
import pluginHostShot from '../../artifacts/raw/24-devtools-plugin-host.avif'
|
||||
import screenCaptureShot from '../../artifacts/raw/25-devtools-screen-capture.avif'
|
||||
import visionCaptureShot from '../../artifacts/raw/26-devtools-vision-capture.avif'
|
||||
import Icon from '../components/icon.vue'
|
||||
|
||||
import { PlatformRoot } from '../components/platforms/macos-26'
|
||||
import { Application } from '../components/platforms/macos-26/containers/dock'
|
||||
import { WindowRoot } from '../components/platforms/macos-26/containers/window'
|
||||
|
||||
/**
|
||||
* These coordinates are expressed in the logical `1920x1080` canvas provided by
|
||||
* `ScenarioCanvas`, not in the browser's live viewport.
|
||||
*
|
||||
* That is why the windows keep their relative placement when the viewport size
|
||||
* changes: the browser scales the entire fixed scene surface after layout rather
|
||||
* than reinterpreting each translate against a resized responsive container.
|
||||
*/
|
||||
const stageWindowStyle = {
|
||||
right: '0px',
|
||||
bottom: '0px',
|
||||
}
|
||||
|
||||
const websocketWindowStyle = {
|
||||
left: '480px',
|
||||
top: '120px',
|
||||
}
|
||||
|
||||
const mainOnlyWindowStyle = {
|
||||
right: '0px',
|
||||
bottom: '0px',
|
||||
}
|
||||
|
||||
const settingsOnlyWindowStyle = {
|
||||
left: '50%',
|
||||
top: '84px',
|
||||
transform: 'translateX(-50%)',
|
||||
}
|
||||
|
||||
const chatWindowStyle = {
|
||||
left: '50%',
|
||||
top: '72px',
|
||||
transform: 'translateX(-50%)',
|
||||
}
|
||||
|
||||
const settingsCaptureRoots = [
|
||||
{ name: 'manual-settings-window', src: settingsShot, imageClass: 'w-130' },
|
||||
{ name: 'manual-websocket-settings', src: websocketSettingsShot, imageClass: 'w-130' },
|
||||
{ name: 'manual-airi-card', src: airiCardShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-providers', src: providersShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-data-settings', src: dataShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-system-general', src: systemGeneralShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-system-color-scheme', src: systemColorSchemeShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-models', src: modelsShot, imageClass: 'w-150' },
|
||||
{ name: 'manual-modules', src: modulesShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-hearing', src: hearingShot, imageClass: 'w-145' },
|
||||
{ name: 'manual-system-developer', src: systemDeveloperShot, imageClass: 'w-145' },
|
||||
{ name: 'manual-consciousness', src: consciousnessShot, imageClass: 'w-145' },
|
||||
{ name: 'manual-speech', src: speechShot, imageClass: 'w-150' },
|
||||
{ name: 'manual-vision', src: visionShot, imageClass: 'w-145' },
|
||||
{ name: 'manual-devtools-use-window-mouse', src: useWindowMouseShot, imageClass: 'w-140' },
|
||||
{ name: 'manual-devtools-displays', src: displaysShot, imageClass: 'w-150' },
|
||||
{ name: 'manual-devtools-widgets-calling', src: widgetsCallingShot, imageClass: 'w-150' },
|
||||
{ name: 'manual-devtools-context-flow', src: contextFlowShot, imageClass: 'w-155' },
|
||||
{ name: 'manual-devtools-relative-mouse', src: relativeMouseShot, imageClass: 'w-150' },
|
||||
{ name: 'manual-devtools-beat-sync', src: beatSyncShot, imageClass: 'w-155' },
|
||||
{ name: 'manual-devtools-websocket-inspector', src: websocketInspectorShot, imageClass: 'w-155' },
|
||||
{ name: 'manual-devtools-plugin-host', src: pluginHostShot, imageClass: 'w-155' },
|
||||
{ name: 'manual-devtools-screen-capture', src: screenCaptureShot, imageClass: 'w-155' },
|
||||
{ name: 'manual-devtools-vision-capture', src: visionCaptureShot, imageClass: 'w-155' },
|
||||
] as const
|
||||
|
||||
async function waitForImageSource(source: string): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const image = new Image()
|
||||
|
||||
image.addEventListener('load', () => resolve(), { once: true })
|
||||
image.addEventListener('error', () => reject(new Error(`Scenario image failed to load: ${source}`)), { once: true })
|
||||
image.src = source
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
resetScenarioReady()
|
||||
try {
|
||||
await Promise.all([
|
||||
document.fonts.ready,
|
||||
waitForImageSource(stageShot),
|
||||
waitForImageSource(controlsIslandShot),
|
||||
waitForImageSource(settingsShot),
|
||||
waitForImageSource(websocketSettingsShot),
|
||||
waitForImageSource(chatWindowShot),
|
||||
waitForImageSource(airiCardShot),
|
||||
waitForImageSource(providersShot),
|
||||
waitForImageSource(dataShot),
|
||||
waitForImageSource(systemGeneralShot),
|
||||
waitForImageSource(systemColorSchemeShot),
|
||||
waitForImageSource(modelsShot),
|
||||
waitForImageSource(modulesShot),
|
||||
waitForImageSource(hearingShot),
|
||||
waitForImageSource(systemDeveloperShot),
|
||||
waitForImageSource(consciousnessShot),
|
||||
waitForImageSource(speechShot),
|
||||
waitForImageSource(visionShot),
|
||||
waitForImageSource(useWindowMouseShot),
|
||||
waitForImageSource(displaysShot),
|
||||
waitForImageSource(widgetsCallingShot),
|
||||
waitForImageSource(contextFlowShot),
|
||||
waitForImageSource(relativeMouseShot),
|
||||
waitForImageSource(beatSyncShot),
|
||||
waitForImageSource(websocketInspectorShot),
|
||||
waitForImageSource(pluginHostShot),
|
||||
waitForImageSource(screenCaptureShot),
|
||||
waitForImageSource(visionCaptureShot),
|
||||
])
|
||||
markScenarioReady()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScenarioCaptureRoot name="manual-main-window">
|
||||
<ScenarioCanvas :width="1920" :height="1080">
|
||||
<PlatformRoot :dock-size="1.5">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="mainOnlyWindowStyle"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="stageShot" class="w-95">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
<template #dock>
|
||||
<Application running>
|
||||
<Icon />
|
||||
</Application>
|
||||
</template>
|
||||
</PlatformRoot>
|
||||
</ScenarioCanvas>
|
||||
</ScenarioCaptureRoot>
|
||||
|
||||
<ScenarioCaptureRoot name="manual-controls-island-expanded">
|
||||
<ScenarioCanvas :width="1920" :height="1080">
|
||||
<PlatformRoot :dock-size="1.5">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="mainOnlyWindowStyle"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="controlsIslandShot" class="w-95">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
<template #dock>
|
||||
<Application running>
|
||||
<Icon />
|
||||
</Application>
|
||||
</template>
|
||||
</PlatformRoot>
|
||||
</ScenarioCanvas>
|
||||
</ScenarioCaptureRoot>
|
||||
|
||||
<ScenarioCaptureRoot
|
||||
v-for="capture in settingsCaptureRoots"
|
||||
:key="capture.name"
|
||||
:name="capture.name"
|
||||
>
|
||||
<ScenarioCanvas :width="1920" :height="1080">
|
||||
<PlatformRoot :dock-size="1.5">
|
||||
<template #windows>
|
||||
<WindowRoot :style="settingsOnlyWindowStyle">
|
||||
<img :src="capture.src" :class="capture.imageClass">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
<template #dock>
|
||||
<Application running>
|
||||
<Icon />
|
||||
</Application>
|
||||
</template>
|
||||
</PlatformRoot>
|
||||
</ScenarioCanvas>
|
||||
</ScenarioCaptureRoot>
|
||||
|
||||
<ScenarioCaptureRoot name="manual-chat-window">
|
||||
<ScenarioCanvas :width="1920" :height="1080">
|
||||
<PlatformRoot :dock-size="1.5">
|
||||
<template #windows>
|
||||
<WindowRoot :style="chatWindowStyle">
|
||||
<img :src="chatWindowShot" class="w-150">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
<template #dock>
|
||||
<Application running>
|
||||
<Icon />
|
||||
</Application>
|
||||
</template>
|
||||
</PlatformRoot>
|
||||
</ScenarioCanvas>
|
||||
</ScenarioCaptureRoot>
|
||||
|
||||
<ScenarioCaptureRoot name="intro-chat-window">
|
||||
<ScenarioCanvas :width="1920" :height="1080">
|
||||
<PlatformRoot :dock-size="1.5">
|
||||
<template #windows>
|
||||
<WindowRoot
|
||||
:style="stageWindowStyle"
|
||||
anchor-to="bottom-right"
|
||||
anchor-bounds="workarea"
|
||||
:frame="false"
|
||||
:has-shadow="false"
|
||||
>
|
||||
<img :src="stageShot" class="w-95">
|
||||
</WindowRoot>
|
||||
<WindowRoot :style="websocketWindowStyle">
|
||||
<img :src="websocketSettingsShot" class="w-120">
|
||||
</WindowRoot>
|
||||
</template>
|
||||
<template #dock>
|
||||
<Application running>
|
||||
<Icon />
|
||||
</Application>
|
||||
</template>
|
||||
</PlatformRoot>
|
||||
</ScenarioCanvas>
|
||||
</ScenarioCaptureRoot>
|
||||
</template>
|
||||
@@ -11,7 +11,8 @@
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"vite/client"
|
||||
"vite/client",
|
||||
"unplugin-vue-router/client"
|
||||
],
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import Unocss from 'unocss/vite'
|
||||
import VueRouter from 'unplugin-vue-router/vite'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
VueRouter({
|
||||
extensions: ['.vue'],
|
||||
dts: resolve(import.meta.dirname, 'src/typed-router.d.ts'),
|
||||
importMode: 'async',
|
||||
routesFolder: [
|
||||
resolve(import.meta.dirname, 'src', 'scenes'),
|
||||
],
|
||||
exclude: ['**/components/**'],
|
||||
}),
|
||||
|
||||
Vue(),
|
||||
|
||||
// https://github.com/antfu/unocss
|
||||
|
||||
@@ -14,17 +14,27 @@ It does not launch Electron itself and it does not own browser-scene composition
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Build the Electron app.
|
||||
2. Run the Electron runner against a section-based scenario entrypoint under `src/scenarios/demo-controls-settings-chat-websocket/index.ts`.
|
||||
3. Write the scenario's raw working outputs into the scenario-local working directory.
|
||||
4. Publish the final docs screenshots directly into `docs/content/en/docs/manual/tamagotchi/setup-and-use/assets`.
|
||||
5. Skip any separate final staging directory for this docs workflow.
|
||||
This package is step 1 of the docs screenshot pipeline.
|
||||
|
||||
1. Build `@proj-airi/stage-tamagotchi`.
|
||||
2. Run this scenario through `@proj-airi/vishot-runner-electron`.
|
||||
3. Write raw outputs to `packages/scenarios-stage-tamagotchi-browser/artifacts/raw`.
|
||||
4. Then run the browser package capture (step 2, documented in that package README).
|
||||
|
||||
## Agent Quickstart
|
||||
|
||||
From repo root, run:
|
||||
|
||||
```bash
|
||||
pnpm -F @proj-airi/stage-tamagotchi build
|
||||
pnpm -F @proj-airi/vishot-runner-electron capture --format avif -- packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket/index.ts --output-dir /tmp/tamagotchi-docs-capture
|
||||
pnpm -F @proj-airi/vishot-runner-electron capture ../../packages/scenarios-stage-tamagotchi-electron/src/scenarios/demo-controls-settings-chat-websocket/index.ts --output-dir ../../packages/scenarios-stage-tamagotchi-browser/artifacts/raw --format avif
|
||||
```
|
||||
|
||||
Expected result:
|
||||
|
||||
- `27` raw files in `packages/scenarios-stage-tamagotchi-browser/artifacts/raw`
|
||||
- names like `00-stage-tamagotchi.avif` ... `26-devtools-vision-capture.avif`
|
||||
|
||||
## Scenario Authoring
|
||||
|
||||
```ts
|
||||
@@ -48,10 +58,16 @@ export default defineScenario({
|
||||
|
||||
## Scenario Layout
|
||||
|
||||
The docs workflow is organized as one section-based scenario module under `src/scenarios/demo-controls-settings-chat-websocket/`. The top-level `index.ts` orchestrates section manifests and writes working outputs into the scenario-local raw directory, so the capture flow stays close to the scenario being authored.
|
||||
The docs workflow is organized as one section-based scenario module under `src/scenarios/demo-controls-settings-chat-websocket/`. The top-level `index.ts` orchestrates section manifests.
|
||||
|
||||
Important:
|
||||
|
||||
- `--output-dir` for the runner should point to `packages/scenarios-stage-tamagotchi-browser/artifacts/raw`.
|
||||
- This package does not publish docs assets directly; it only prepares raw assets for browser-scene composition.
|
||||
|
||||
## Notes
|
||||
|
||||
- Raw scenario modules live under `src/scenarios`.
|
||||
- Scenario entrypoints should point at `index.ts` when the workflow is organized as a section folder.
|
||||
- Keep this package focused on Electron capture flows for docs screenshots.
|
||||
- Paths in these `pnpm -F` examples are resolved from the filtered package working directory.
|
||||
|
||||
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 17 KiB |