evomeme 0.1.10-beta.1 → 0.1.10-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/canvas-public/canvas/actions.js +2 -1
- package/dist/canvas-public/canvas/chrome.js +2 -1
- package/dist/canvas-public/core/api.js +8 -3
- package/dist/canvas-public/core/layout.js +3 -3
- package/dist/canvas-public/elements/task-pipeline.js +4 -3
- package/dist/canvas-public/features/assets/official-assets.js +4 -3
- package/dist/canvas-public/features/editing/canvas-size.js +2 -2
- package/dist/canvas-public/features/editing/color-key.js +2 -0
- package/dist/canvas-public/features/editing/interactive-image-editing.js +9 -8
- package/dist/canvas-public/features/editing/layer-decomposition.js +2 -1
- package/dist/canvas-public/features/editing/manual-split.js +2 -1
- package/dist/canvas-public/features/editing/perfect-pixel.js +56 -93
- package/dist/canvas-public/features/editing/scroll-preview.js +3 -2
- package/dist/canvas-public/features/editing/smart-split.js +2 -1
- package/dist/canvas-public/features/editing/source-preview.js +2 -2
- package/dist/canvas-public/features/frame-editor/frame-editing.js +5 -5
- package/dist/canvas-public/features/frame-editor/preview.js +4 -2
- package/dist/canvas-public/features/generation/image.js +25 -25
- package/dist/canvas-public/features/map/tile-map-editor.js +7 -5
- package/dist/canvas-public/language/index.js +14 -2
- package/dist/canvas-public/map/export.js +2 -1
- package/dist/canvas-public/ui/feature-form.js +2 -1
- package/dist/canvas-public/ui/settings.js +2 -1
- package/dist/{canvas-public/workers/perfect-pixel-worker.js → image-workers/perfect-pixel/perfect-pixel-core.js} +3 -72
- package/dist/image-workers/perfect-pixel/perfect-pixel-worker.cjs +10 -0
- package/dist/index.js +13043 -7681
- package/dist/skills/en-US/evomeme-cloud-fs/SKILL.md +30 -3
- package/dist/skills/en-US/evomeme-cover/SKILL.md +7 -8
- package/dist/skills/en-US/evomeme-login/SKILL.md +5 -0
- package/dist/skills/en-US/evomeme-mcp/SKILL.md +71 -0
- package/dist/skills/en-US/evomeme-mcp/agents/openai.yaml +7 -0
- package/dist/skills/en-US/evomeme-mcp/references/troubleshooting.md +55 -0
- package/dist/skills/en-US/game-asset-align/SKILL.md +6 -4
- package/dist/skills/en-US/game-asset-align/references/blender-scene-pipeline.md +7 -17
- package/dist/skills/en-US/game-asset-align/references/canonical-package.md +1 -1
- package/dist/skills/en-US/game-asset-align/references/lightweight-review.md +7 -5
- package/dist/skills/en-US/game-asset-align/references/repair-policy.md +2 -2
- package/dist/skills/en-US/game-asset-align/references/tool-contract.md +47 -17
- package/dist/skills/en-US/game-asset-batch-generator/SKILL.md +117 -0
- package/dist/skills/en-US/game-asset-batch-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-asset-image-generator/SKILL.md +53 -71
- package/dist/skills/en-US/game-asset-image-generator/references/background.md +3 -1
- package/dist/skills/en-US/game-asset-image-generator/references/static-game-assets.md +2 -2
- package/dist/skills/en-US/game-asset-mesh-generator/SKILL.md +35 -0
- package/dist/skills/en-US/game-asset-mesh-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-asset-mesh-generator/references/animation-presets.md +313 -0
- package/dist/skills/en-US/game-asset-mesh-generator/references/generation-contracts.md +131 -0
- package/dist/skills/en-US/game-asset-mesh-generator/references/image-generator-workflows.md +65 -0
- package/dist/skills/en-US/game-asset-mesh-generator/references/postprocess-contracts.md +187 -0
- package/dist/skills/en-US/game-asset-mesh-generator/references/threejs-integration.md +113 -0
- package/dist/skills/en-US/game-asset-mesh-generator/references/tool-reference.md +67 -0
- package/dist/skills/en-US/game-audio/SKILL.md +55 -16
- package/dist/skills/en-US/game-audio/agents/openai.yaml +2 -2
- package/dist/skills/en-US/game-audio/references/bgm-cdn.md +7 -11
- package/dist/skills/en-US/game-audio/references/sfx-cdn.md +6 -4
- package/dist/skills/en-US/game-character-view-generator/SKILL.md +110 -0
- package/dist/skills/en-US/game-character-view-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-image-layer-decomposer/SKILL.md +99 -0
- package/dist/skills/en-US/game-image-layer-decomposer/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-image-splitter/SKILL.md +114 -0
- package/dist/skills/en-US/game-image-splitter/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-perfect-pixel/SKILL.md +123 -0
- package/dist/skills/en-US/game-perfect-pixel/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-scroll-generator/SKILL.md +110 -0
- package/dist/skills/en-US/game-scroll-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-tile-generator/SKILL.md +152 -0
- package/dist/skills/en-US/game-tile-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-ui-generator/SKILL.md +82 -0
- package/dist/skills/en-US/game-ui-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-ui-generator/references/component-patterns.md +65 -0
- package/dist/skills/en-US/game-ui-generator/references/runtime-integration.md +45 -0
- package/dist/skills/en-US/game-video-cycle/SKILL.md +98 -0
- package/dist/skills/en-US/game-video-cycle/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-video-generator/SKILL.md +78 -0
- package/dist/skills/en-US/game-video-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/godot-3d-game/SKILL.md +4 -2
- package/dist/skills/en-US/godot-3d-game/references/alignment-gates.md +1 -1
- package/dist/skills/en-US/godot-3d-game/references/appearance-alignment.md +4 -2
- package/dist/skills/en-US/threejs-3d-game/SKILL.md +3 -3
- package/dist/skills/en-US/threejs-3d-game/references/engine-asset-pipeline.md +5 -3
- package/dist/skills/en-US/threejs-aaa-graphics-builder/SKILL.md +6 -6
- package/dist/skills/zh-CN/evomeme-cloud-fs/SKILL.md +31 -4
- package/dist/skills/zh-CN/evomeme-cover/SKILL.md +7 -8
- package/dist/skills/zh-CN/evomeme-login/SKILL.md +5 -0
- package/dist/skills/zh-CN/evomeme-mcp/SKILL.md +71 -0
- package/dist/skills/zh-CN/evomeme-mcp/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/evomeme-mcp/references/troubleshooting.md +55 -0
- package/dist/skills/zh-CN/game-asset-align/SKILL.md +6 -4
- package/dist/skills/zh-CN/game-asset-align/references/blender-scene-pipeline.md +7 -17
- package/dist/skills/zh-CN/game-asset-align/references/canonical-package.md +1 -1
- package/dist/skills/zh-CN/game-asset-align/references/lightweight-review.md +7 -5
- package/dist/skills/zh-CN/game-asset-align/references/repair-policy.md +2 -2
- package/dist/skills/zh-CN/game-asset-align/references/tool-contract.md +48 -18
- package/dist/skills/zh-CN/game-asset-batch-generator/SKILL.md +117 -0
- package/dist/skills/zh-CN/game-asset-batch-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-asset-image-generator/SKILL.md +53 -71
- package/dist/skills/zh-CN/game-asset-image-generator/references/background.md +3 -1
- package/dist/skills/zh-CN/game-asset-image-generator/references/static-game-assets.md +2 -2
- package/dist/skills/zh-CN/game-asset-mesh-generator/SKILL.md +35 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/references/animation-presets.md +313 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/references/generation-contracts.md +131 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/references/image-generator-workflows.md +65 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/references/postprocess-contracts.md +187 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/references/threejs-integration.md +113 -0
- package/dist/skills/zh-CN/game-asset-mesh-generator/references/tool-reference.md +67 -0
- package/dist/skills/zh-CN/game-audio/SKILL.md +55 -16
- package/dist/skills/zh-CN/game-audio/agents/openai.yaml +2 -2
- package/dist/skills/zh-CN/game-audio/references/bgm-cdn.md +7 -11
- package/dist/skills/zh-CN/game-audio/references/sfx-cdn.md +6 -4
- package/dist/skills/zh-CN/game-character-view-generator/SKILL.md +110 -0
- package/dist/skills/zh-CN/game-character-view-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-image-layer-decomposer/SKILL.md +99 -0
- package/dist/skills/zh-CN/game-image-layer-decomposer/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-image-splitter/SKILL.md +114 -0
- package/dist/skills/zh-CN/game-image-splitter/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-perfect-pixel/SKILL.md +123 -0
- package/dist/skills/zh-CN/game-perfect-pixel/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-scroll-generator/SKILL.md +110 -0
- package/dist/skills/zh-CN/game-scroll-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-tile-generator/SKILL.md +151 -0
- package/dist/skills/zh-CN/game-tile-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-ui-generator/SKILL.md +82 -0
- package/dist/skills/zh-CN/game-ui-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-ui-generator/references/component-patterns.md +65 -0
- package/dist/skills/zh-CN/game-ui-generator/references/runtime-integration.md +45 -0
- package/dist/skills/zh-CN/game-video-cycle/SKILL.md +98 -0
- package/dist/skills/zh-CN/game-video-cycle/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/game-video-generator/SKILL.md +78 -0
- package/dist/skills/zh-CN/game-video-generator/agents/openai.yaml +7 -0
- package/dist/skills/zh-CN/godot-3d-game/SKILL.md +4 -2
- package/dist/skills/zh-CN/godot-3d-game/references/alignment-gates.md +1 -1
- package/dist/skills/zh-CN/godot-3d-game/references/appearance-alignment.md +4 -2
- package/dist/skills/zh-CN/threejs-3d-game/SKILL.md +3 -3
- package/dist/skills/zh-CN/threejs-3d-game/references/engine-asset-pipeline.md +5 -3
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/SKILL.md +6 -6
- package/dist/stage-public/app.js +1 -1
- package/dist/stage-public/delivery.js +2 -2
- package/dist/stage-public/review-app.js +7 -7
- package/dist/stage-public/review-runtime.js +1 -1
- package/dist/stage-public/scene-runtime.js +11 -11
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
npm 包名:`evomeme`。用 `npx` 安装到 AI 工具后,即可在对话里做游戏、生图、提交草稿。
|
|
7
7
|
|
|
8
|
+
本地 3D 资产工作流默认使用 `evomeme asset`、`evomeme scene export` 和 `evomeme stage`,详见 [资产 CLI 使用说明](../docs/asset-cli.md)。IDE 插件安装不等于全局安装 CLI:使用已发布且支持这些命令的 `npx -y evomeme@latest`,或本地构建后的 `node dist/index.js`;先执行 `asset --help` 检查能力。这 12 个资产/Stage MCP 入口已移除,对应操作使用 CLI;其余 20 个 MCP 工具继续可用。
|
|
9
|
+
|
|
8
10
|
---
|
|
9
11
|
|
|
10
12
|
## 一、这是什么?
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../language/index.js'
|
|
1
2
|
import { removeFeatureReferences } from '../core/feature-state.js'
|
|
2
3
|
import { t } from '../language/index.js'
|
|
3
4
|
import { api } from '../core/api.js'
|
|
@@ -52,7 +53,7 @@ export function createCanvasActions({
|
|
|
52
53
|
body: '{}',
|
|
53
54
|
})
|
|
54
55
|
const assets = Array.isArray(result.assets) ? result.assets : []
|
|
55
|
-
if (!assets.length) throw new Error(
|
|
56
|
+
if (!assets.length) throw new Error(errorT("canvas.actions.text001"))
|
|
56
57
|
const ids = assets.map((asset) => asset.id)
|
|
57
58
|
const columns = Math.min(5, Math.max(1, Math.ceil(Math.sqrt(assets.length))))
|
|
58
59
|
assets.forEach((asset, index) => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { errorText } from '../language/index.js'
|
|
1
2
|
import { state } from '../core/state.js'
|
|
2
3
|
|
|
3
4
|
// Form surfaces subscribe to feature state; chrome does not mirror business DOM.
|
|
4
5
|
export function createChromeController({ elements, renderInspector }) {
|
|
5
6
|
return { renderChrome() {
|
|
6
7
|
elements.bannerError.hidden = !state.error
|
|
7
|
-
elements.bannerError.textContent = state.error || ''
|
|
8
|
+
elements.bannerError.textContent = errorText(state.error) || ''
|
|
8
9
|
renderInspector()
|
|
9
10
|
} }
|
|
10
11
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t } from '../language/index.js'
|
|
1
|
+
import { t, errorText } from '../language/index.js'
|
|
2
2
|
export async function api(path, options) {
|
|
3
3
|
const headers = new Headers(options?.headers)
|
|
4
4
|
if (options?.method === 'POST') headers.set('Prefer', 'respond-async')
|
|
@@ -9,7 +9,8 @@ export async function api(path, options) {
|
|
|
9
9
|
const message = Array.isArray(detail)
|
|
10
10
|
? detail.map((d) => d.msg || JSON.stringify(d)).join('; ')
|
|
11
11
|
: detail || data.message || res.statusText || t("core.api.text001")
|
|
12
|
-
const error = new Error(message)
|
|
12
|
+
const error = new Error(errorText(data.error?.message || message))
|
|
13
|
+
error.errorInfo = data.error
|
|
13
14
|
error.status = res.status
|
|
14
15
|
error.data = data
|
|
15
16
|
throw error
|
|
@@ -18,7 +19,11 @@ export async function api(path, options) {
|
|
|
18
19
|
for (;;) {
|
|
19
20
|
const task = await api(`/api/tasks/${encodeURIComponent(data.task_id)}`)
|
|
20
21
|
if (task.status === 'SUCCEEDED') return task.result || {}
|
|
21
|
-
if (['FAILED', 'NEEDS_ATTENTION'].includes(task.status))
|
|
22
|
+
if (['FAILED', 'NEEDS_ATTENTION'].includes(task.status)) {
|
|
23
|
+
const error = new Error(errorText(task.errorInfo?.message || task.error || t("core.api.text002")))
|
|
24
|
+
error.errorInfo = task.errorInfo
|
|
25
|
+
throw error
|
|
26
|
+
}
|
|
22
27
|
// INTERRUPTED is recoverable; the scheduler continues polling the saved backend job.
|
|
23
28
|
await new Promise(resolve => setTimeout(resolve, 750))
|
|
24
29
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t } from '../language/index.js'
|
|
1
|
+
import { t, errorT } from '../language/index.js'
|
|
2
2
|
import { api } from './api.js'
|
|
3
3
|
import { state } from './state.js'
|
|
4
4
|
|
|
@@ -78,7 +78,7 @@ export function createLayoutController({
|
|
|
78
78
|
const request = saveQueue.then(async () => {
|
|
79
79
|
try {
|
|
80
80
|
if (epoch !== saveEpoch) {
|
|
81
|
-
const staleError = new Error(
|
|
81
|
+
const staleError = new Error(errorT("core.layout.text001"))
|
|
82
82
|
staleError.status = 409
|
|
83
83
|
throw staleError
|
|
84
84
|
}
|
|
@@ -102,7 +102,7 @@ export function createLayoutController({
|
|
|
102
102
|
const firstFailureInEpoch = epoch === saveEpoch
|
|
103
103
|
if (firstFailureInEpoch) saveEpoch += 1
|
|
104
104
|
if (error?.status === 409 && firstFailureInEpoch) {
|
|
105
|
-
state.error =
|
|
105
|
+
state.error = errorT("core.layout.text001")
|
|
106
106
|
await onConflict().catch(() => undefined)
|
|
107
107
|
}
|
|
108
108
|
throw error
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../language/index.js'
|
|
1
2
|
import { t } from '../language/index.js'
|
|
2
3
|
import { api } from '../core/api.js'
|
|
3
4
|
import { state } from '../core/state.js'
|
|
@@ -129,7 +130,7 @@ export function createCanvasTaskPipeline({
|
|
|
129
130
|
function insertPendingItem(source, pending, placement, options = {}) {
|
|
130
131
|
if (placement === 'replace') {
|
|
131
132
|
if (!replaceGenerationInputWithPending(source?.id, pending, options)) {
|
|
132
|
-
throw new Error(
|
|
133
|
+
throw new Error(errorT("elements.taskPipeline.text002"))
|
|
133
134
|
}
|
|
134
135
|
return
|
|
135
136
|
}
|
|
@@ -188,8 +189,8 @@ export function createCanvasTaskPipeline({
|
|
|
188
189
|
onFailure,
|
|
189
190
|
onSettled,
|
|
190
191
|
}) {
|
|
191
|
-
if (!source?.id) throw new Error(
|
|
192
|
-
if (typeof execute !== 'function') throw new Error(
|
|
192
|
+
if (!source?.id) throw new Error(errorT("elements.taskPipeline.text003"))
|
|
193
|
+
if (typeof execute !== 'function') throw new Error(errorT("elements.taskPipeline.text004"))
|
|
193
194
|
|
|
194
195
|
const operationId = wait.id || createCanvasClientId('pending')
|
|
195
196
|
const pending = createPendingItem(source, operationId, wait, placement)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT, errorText } from '../../language/index.js'
|
|
1
2
|
import { ensureFeatureState, peekFeatureState } from '../../core/feature-state.js'
|
|
2
3
|
import { t } from '../../language/index.js'
|
|
3
4
|
import { api } from '../../core/api.js'
|
|
@@ -312,7 +313,7 @@ function createOfficialAssetsTool(context) {
|
|
|
312
313
|
)
|
|
313
314
|
const catalog = await response.json()
|
|
314
315
|
if (!Array.isArray(catalog?.categories))
|
|
315
|
-
throw new Error(
|
|
316
|
+
throw new Error(errorT('features.assets.officialAssets.text006'))
|
|
316
317
|
officialAssetCatalog = catalog
|
|
317
318
|
return catalog
|
|
318
319
|
})
|
|
@@ -371,7 +372,7 @@ function createOfficialAssetsTool(context) {
|
|
|
371
372
|
body: JSON.stringify({ files, place: false }),
|
|
372
373
|
})
|
|
373
374
|
const imported = Array.isArray(result.images) ? result.images : []
|
|
374
|
-
if (!imported.length) throw new Error(
|
|
375
|
+
if (!imported.length) throw new Error(errorT('features.assets.officialAssets.text008'))
|
|
375
376
|
importedIds = imported.map((asset) => asset.id).filter(Boolean)
|
|
376
377
|
|
|
377
378
|
const resultItems = layoutCanvasElementsAtSlot(
|
|
@@ -499,7 +500,7 @@ function createOfficialAssetsTool(context) {
|
|
|
499
500
|
const footer = el('div', 'official-assets-footer')
|
|
500
501
|
const feedback = el('div', `official-assets-feedback${ds.error ? ' is-error' : ''}`)
|
|
501
502
|
feedback.textContent =
|
|
502
|
-
ds.error ||
|
|
503
|
+
errorText(ds.error) ||
|
|
503
504
|
ds.status ||
|
|
504
505
|
`${t('features.assets.officialAssets.text017')}${ds.selectedIds.size}${t('features.assets.officialAssets.text017.part1')}`
|
|
505
506
|
const actions = el('div', 'official-assets-actions')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t } from '../../language/index.js'
|
|
1
|
+
import { t, errorT } from '../../language/index.js'
|
|
2
2
|
import {
|
|
3
3
|
ensureFeatureState,
|
|
4
4
|
notifyFeatureState,
|
|
@@ -306,7 +306,7 @@ export class ImageCanvasSizeEditor {
|
|
|
306
306
|
const image = new Image()
|
|
307
307
|
await new Promise((resolve, reject) => {
|
|
308
308
|
image.onload = resolve
|
|
309
|
-
image.onerror = () => reject(new Error(
|
|
309
|
+
image.onerror = () => reject(new Error(errorT('features.editing.imageCanvasSizeEditor.text001')))
|
|
310
310
|
image.src = url
|
|
311
311
|
})
|
|
312
312
|
this.image = image
|
|
@@ -129,6 +129,7 @@ function createColorKeyTool(context) {
|
|
|
129
129
|
const canvas = findNodeEl(editorId)?.querySelector('[data-role="color-key-preview"]')
|
|
130
130
|
if (!ds || !canvas || !previewUrl) return
|
|
131
131
|
const image = new Image()
|
|
132
|
+
image.crossOrigin = 'anonymous'
|
|
132
133
|
image.onload = () => {
|
|
133
134
|
if (!canvas.isConnected || peekFeatureState(editorId)?.previewUrl !== previewUrl) return
|
|
134
135
|
const scale = Math.min(1, 900 / Math.max(image.naturalWidth, image.naturalHeight))
|
|
@@ -161,6 +162,7 @@ function createColorKeyTool(context) {
|
|
|
161
162
|
return
|
|
162
163
|
}
|
|
163
164
|
const image = new Image()
|
|
165
|
+
image.crossOrigin = 'anonymous'
|
|
164
166
|
image.onload = () => {
|
|
165
167
|
const current = peekFeatureState(editorId)
|
|
166
168
|
const currentCanvas = findNodeEl(editorId)?.querySelector('[data-role="color-key-preview"]')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT, errorText } from '../../language/index.js'
|
|
1
2
|
import { t } from '../../language/index.js'
|
|
2
3
|
import { api } from '../../core/api.js'
|
|
3
4
|
import { state } from '../../core/state.js'
|
|
@@ -400,7 +401,7 @@ function createInteractiveImageEditing(services) {
|
|
|
400
401
|
async task(item, params) {
|
|
401
402
|
const referenceImageData = await views.get(item.id)?.exportImage()
|
|
402
403
|
if (!referenceImageData?.startsWith('data:image/'))
|
|
403
|
-
throw new Error(
|
|
404
|
+
throw new Error(errorT('features.editing.interactiveImageEditing.text036'))
|
|
404
405
|
params.referenceImageData = referenceImageData
|
|
405
406
|
return {
|
|
406
407
|
endpoint: '/api/generate',
|
|
@@ -556,7 +557,7 @@ function mountDrawingView(host, item, services) {
|
|
|
556
557
|
interactiveEditorExportRequest = null
|
|
557
558
|
if (exportRequest) {
|
|
558
559
|
clearTimeout(exportRequest.timeout)
|
|
559
|
-
exportRequest.reject(new Error(
|
|
560
|
+
exportRequest.reject(new Error(errorT('features.editing.interactiveImageEditing.text024')))
|
|
560
561
|
}
|
|
561
562
|
if (editor) {
|
|
562
563
|
try {
|
|
@@ -572,7 +573,7 @@ function mountDrawingView(host, item, services) {
|
|
|
572
573
|
try {
|
|
573
574
|
const PainterroEditor = window.Painterro
|
|
574
575
|
if (typeof PainterroEditor !== 'function')
|
|
575
|
-
throw new Error(
|
|
576
|
+
throw new Error(errorT('features.editing.interactiveImageEditing.text027'))
|
|
576
577
|
if (disposed) return
|
|
577
578
|
let editor = null
|
|
578
579
|
let loadTimeout = null
|
|
@@ -616,7 +617,7 @@ function mountDrawingView(host, item, services) {
|
|
|
616
617
|
},
|
|
617
618
|
onImageLoaded: () => finish(resolve),
|
|
618
619
|
onImageFailedOpen: () =>
|
|
619
|
-
finish(reject, new Error(
|
|
620
|
+
finish(reject, new Error(errorT('features.editing.interactiveImageEditing.text028'))),
|
|
620
621
|
saveHandler: (image, done) => {
|
|
621
622
|
const request = interactiveEditorExportRequest
|
|
622
623
|
if (!request || request.editor !== editor) {
|
|
@@ -638,7 +639,7 @@ function mountDrawingView(host, item, services) {
|
|
|
638
639
|
interactiveEditorInteractionCleanup = installInteractiveEditorInteractions(editor)
|
|
639
640
|
editor.show(params.referenceImageData || imageUrl(imageId))
|
|
640
641
|
loadTimeout = setTimeout(
|
|
641
|
-
() => reject(new Error(
|
|
642
|
+
() => reject(new Error(errorT('features.editing.interactiveImageEditing.text029'))),
|
|
642
643
|
10_000,
|
|
643
644
|
)
|
|
644
645
|
})
|
|
@@ -654,7 +655,7 @@ function mountDrawingView(host, item, services) {
|
|
|
654
655
|
params.ready = false
|
|
655
656
|
notifyFeatureState(item.id)
|
|
656
657
|
elements.interactiveEditStatus.hidden = false
|
|
657
|
-
elements.interactiveEditStatus.textContent =
|
|
658
|
+
elements.interactiveEditStatus.textContent = errorText(error)
|
|
658
659
|
}
|
|
659
660
|
}
|
|
660
661
|
|
|
@@ -893,13 +894,13 @@ function mountDrawingView(host, item, services) {
|
|
|
893
894
|
|
|
894
895
|
function exportInteractiveEditor(editor) {
|
|
895
896
|
if (interactiveEditorExportRequest) {
|
|
896
|
-
return Promise.reject(new Error(
|
|
897
|
+
return Promise.reject(new Error(errorT('features.editing.interactiveImageEditing.text033')))
|
|
897
898
|
}
|
|
898
899
|
return new Promise((resolve, reject) => {
|
|
899
900
|
const timeout = setTimeout(() => {
|
|
900
901
|
if (interactiveEditorExportRequest?.editor !== editor) return
|
|
901
902
|
interactiveEditorExportRequest = null
|
|
902
|
-
reject(new Error(
|
|
903
|
+
reject(new Error(errorT('features.editing.interactiveImageEditing.text034')))
|
|
903
904
|
}, 10_000)
|
|
904
905
|
interactiveEditorExportRequest = { editor, resolve, reject, timeout }
|
|
905
906
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../../language/index.js'
|
|
1
2
|
import { t } from '../../language/index.js'
|
|
2
3
|
export default {
|
|
3
4
|
render: (ui) => ui.prompt('prompt'),
|
|
@@ -37,7 +38,7 @@ export default {
|
|
|
37
38
|
},
|
|
38
39
|
checkResult(result) {
|
|
39
40
|
if (!result.images?.length)
|
|
40
|
-
throw new Error(
|
|
41
|
+
throw new Error(errorT('features.editing.interactiveImageEditing.text047'))
|
|
41
42
|
},
|
|
42
43
|
}
|
|
43
44
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../../language/index.js'
|
|
1
2
|
import { t } from '../../language/index.js'
|
|
2
3
|
import { ensureFeatureState, subscribeFeatureState } from '../../core/feature-state.js'
|
|
3
4
|
import { el } from '../../ui/controls.js'
|
|
@@ -103,7 +104,7 @@ export default {
|
|
|
103
104
|
checkResult(result) {
|
|
104
105
|
if ((result.images || []).length !== p.rows * p.columns)
|
|
105
106
|
throw new Error(
|
|
106
|
-
|
|
107
|
+
errorT('features.editing.imageSplitting.text012') +
|
|
107
108
|
(result.images || []).length +
|
|
108
109
|
t('features.editing.imageSplitting.text012.part1') +
|
|
109
110
|
p.rows * p.columns,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
peekFeatureState,
|
|
4
4
|
notifyFeatureState,
|
|
5
5
|
} from '../../core/feature-state.js'
|
|
6
|
-
import { t,
|
|
6
|
+
import { t, errorT, errorText } from '../../language/index.js'
|
|
7
7
|
import { api } from '../../core/api.js'
|
|
8
8
|
import { state } from '../../core/state.js'
|
|
9
9
|
import { el } from '../../ui/controls.js'
|
|
@@ -85,7 +85,7 @@ export default {
|
|
|
85
85
|
},
|
|
86
86
|
],
|
|
87
87
|
runtimeDefaults: () => ({
|
|
88
|
-
|
|
88
|
+
previewController: null,
|
|
89
89
|
processTimer: null,
|
|
90
90
|
layoutSaveTimer: null,
|
|
91
91
|
requestId: 0,
|
|
@@ -103,21 +103,14 @@ export default {
|
|
|
103
103
|
function createPerfectPixelTool(context) {
|
|
104
104
|
const { findNodeEl, formatPerfectPixelSourceArea, imageUrl, saveLayout } = context
|
|
105
105
|
|
|
106
|
-
function workerErrorText(message) {
|
|
107
|
-
if (message?.messageKey === 'workers.imageLoadFailed') {
|
|
108
|
-
return `${t('workers.imageLoadFailed')}${message.messageVariables?.status ?? ''}`
|
|
109
|
-
}
|
|
110
|
-
return translateWorkerError(message)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
106
|
function perfectPixelSettings(ds) {
|
|
114
107
|
return {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
auto_grid: ds.autoGrid,
|
|
109
|
+
pixel_size: ds.pixelSize,
|
|
110
|
+
sample_method: ds.sampleMethod,
|
|
111
|
+
export_scale: ds.exportScale,
|
|
112
|
+
alpha_mode: ds.alphaMode,
|
|
113
|
+
alpha_threshold: ds.alphaThreshold,
|
|
121
114
|
}
|
|
122
115
|
}
|
|
123
116
|
|
|
@@ -132,7 +125,7 @@ function createPerfectPixelTool(context) {
|
|
|
132
125
|
if (status) {
|
|
133
126
|
status.classList.toggle('is-error', Boolean(ds.error))
|
|
134
127
|
if (ds.error) {
|
|
135
|
-
status.textContent = ds.error
|
|
128
|
+
status.textContent = errorText(ds.error)
|
|
136
129
|
} else if (ds.resultMeta) {
|
|
137
130
|
const meta = ds.resultMeta
|
|
138
131
|
const confidence = meta.automatic
|
|
@@ -157,78 +150,60 @@ function createPerfectPixelTool(context) {
|
|
|
157
150
|
}, 220)
|
|
158
151
|
}
|
|
159
152
|
|
|
160
|
-
function
|
|
153
|
+
async function startPerfectPixelPreview(editorId, sourceId, requestId) {
|
|
161
154
|
const ds = peekFeatureState(editorId)
|
|
162
155
|
if (!ds || ds.requestId !== requestId) return
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
settings: perfectPixelSettings(current),
|
|
175
|
-
})
|
|
176
|
-
return
|
|
156
|
+
const controller = new AbortController()
|
|
157
|
+
ds.previewController = controller
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch('/api/perfect-pixel/preview', {
|
|
160
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
161
|
+
body: JSON.stringify({ id: sourceId, ...perfectPixelSettings(ds) }),
|
|
162
|
+
signal: controller.signal,
|
|
163
|
+
})
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const error = await response.json().catch(() => ({}))
|
|
166
|
+
throw new Error(error.detail || response.statusText)
|
|
177
167
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
pixelSizeY: message.pixelSizeY,
|
|
191
|
-
confidence: message.confidence,
|
|
192
|
-
automatic: message.automatic,
|
|
193
|
-
}
|
|
194
|
-
current.pending = false
|
|
195
|
-
current.error = ''
|
|
196
|
-
const image = findNodeEl(editorId)?.querySelector('[data-role="perfect-pixel-preview"]')
|
|
197
|
-
if (image) image.src = current.previewUrl
|
|
198
|
-
updatePerfectPixelEditorUi(editorId)
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
if (message.type === 'error') {
|
|
202
|
-
worker.terminate()
|
|
203
|
-
current.worker = null
|
|
204
|
-
current.pending = false
|
|
205
|
-
current.resultBlob = null
|
|
206
|
-
current.resultMeta = null
|
|
207
|
-
current.error = `${t('features.editing.perfectPixel.text007')}${workerErrorText(message)}`
|
|
208
|
-
updatePerfectPixelEditorUi(editorId)
|
|
168
|
+
const meta = JSON.parse(response.headers.get('X-Perfect-Pixel-Metadata'))
|
|
169
|
+
const blob = await response.blob()
|
|
170
|
+
const current = peekFeatureState(editorId)
|
|
171
|
+
if (!current || current.requestId !== requestId || controller.signal.aborted) return
|
|
172
|
+
if (current.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(current.previewUrl)
|
|
173
|
+
current.resultBlob = blob
|
|
174
|
+
current.previewUrl = URL.createObjectURL(blob)
|
|
175
|
+
current.resultMeta = {
|
|
176
|
+
width: meta.width, height: meta.height,
|
|
177
|
+
gridWidth: meta.grid_width, gridHeight: meta.grid_height,
|
|
178
|
+
pixelSizeX: meta.pixel_size_x, pixelSizeY: meta.pixel_size_y,
|
|
179
|
+
confidence: meta.confidence, automatic: meta.auto_grid,
|
|
209
180
|
}
|
|
210
|
-
|
|
211
|
-
|
|
181
|
+
current.error = ''
|
|
182
|
+
const image = findNodeEl(editorId)?.querySelector('[data-role="perfect-pixel-preview"]')
|
|
183
|
+
if (image) image.src = current.previewUrl
|
|
184
|
+
} catch (error) {
|
|
212
185
|
const current = peekFeatureState(editorId)
|
|
213
|
-
if (!current || current.
|
|
214
|
-
worker.terminate()
|
|
215
|
-
current.worker = null
|
|
216
|
-
current.pending = false
|
|
186
|
+
if (!current || current.requestId !== requestId || controller.signal.aborted) return
|
|
217
187
|
current.resultBlob = null
|
|
218
188
|
current.resultMeta = null
|
|
219
|
-
current.error =
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
189
|
+
current.error = errorT('features.editing.perfectPixel.text007') + error.message
|
|
190
|
+
} finally {
|
|
191
|
+
const current = peekFeatureState(editorId)
|
|
192
|
+
if (current?.requestId === requestId && !controller.signal.aborted) {
|
|
193
|
+
current.previewController = null
|
|
194
|
+
current.pending = false
|
|
195
|
+
updatePerfectPixelEditorUi(editorId)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
223
198
|
}
|
|
224
199
|
|
|
225
|
-
function schedulePerfectPixelProcess(editorId, delay =
|
|
200
|
+
function schedulePerfectPixelProcess(editorId, delay = 200) {
|
|
226
201
|
const ds = peekFeatureState(editorId)
|
|
227
202
|
const item = state.layoutItems.find((candidate) => candidate.id === editorId)
|
|
228
203
|
if (!ds || !item?.sourceId || ds.busy) return
|
|
229
204
|
clearTimeout(ds.processTimer)
|
|
230
|
-
ds.
|
|
231
|
-
ds.
|
|
205
|
+
ds.previewController?.abort()
|
|
206
|
+
ds.previewController = null
|
|
232
207
|
ds.requestId += 1
|
|
233
208
|
const requestId = ds.requestId
|
|
234
209
|
if (ds.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(ds.previewUrl)
|
|
@@ -240,7 +215,7 @@ function createPerfectPixelTool(context) {
|
|
|
240
215
|
updatePerfectPixelEditorUi(editorId)
|
|
241
216
|
ds.processTimer = setTimeout(() => {
|
|
242
217
|
ds.processTimer = null
|
|
243
|
-
|
|
218
|
+
void startPerfectPixelPreview(editorId, item.sourceId, requestId)
|
|
244
219
|
}, delay)
|
|
245
220
|
}
|
|
246
221
|
|
|
@@ -266,25 +241,12 @@ function createPerfectPixelTool(context) {
|
|
|
266
241
|
if (!ds) return
|
|
267
242
|
clearTimeout(ds.processTimer)
|
|
268
243
|
clearTimeout(ds.layoutSaveTimer)
|
|
269
|
-
ds.
|
|
244
|
+
ds.previewController?.abort()
|
|
270
245
|
if (ds.previewUrl?.startsWith('blob:')) URL.revokeObjectURL(ds.previewUrl)
|
|
271
246
|
}
|
|
272
247
|
|
|
273
248
|
function task(item, ds) {
|
|
274
|
-
const
|
|
275
|
-
const settings = {
|
|
276
|
-
source_id: item.sourceId,
|
|
277
|
-
auto_grid: String(ds.autoGrid),
|
|
278
|
-
grid_width: String(ds.resultMeta.gridWidth),
|
|
279
|
-
grid_height: String(ds.resultMeta.gridHeight),
|
|
280
|
-
pixel_size: String(ds.pixelSize),
|
|
281
|
-
pixel_size_x: String(ds.resultMeta.pixelSizeX),
|
|
282
|
-
pixel_size_y: String(ds.resultMeta.pixelSizeY),
|
|
283
|
-
sample_method: ds.sampleMethod,
|
|
284
|
-
export_scale: String(ds.exportScale),
|
|
285
|
-
alpha_mode: ds.alphaMode,
|
|
286
|
-
alpha_threshold: String(ds.alphaThreshold),
|
|
287
|
-
}
|
|
249
|
+
const settings = { id: item.sourceId, ...perfectPixelSettings(ds) }
|
|
288
250
|
return {
|
|
289
251
|
wait: {
|
|
290
252
|
name: t('features.editing.perfectPixel.text009'),
|
|
@@ -294,8 +256,9 @@ function createPerfectPixelTool(context) {
|
|
|
294
256
|
},
|
|
295
257
|
request: (operationId) =>
|
|
296
258
|
api(
|
|
297
|
-
|
|
298
|
-
{ method: 'POST', headers: { 'Content-Type': '
|
|
259
|
+
'/api/perfect-pixel/apply',
|
|
260
|
+
{ method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
261
|
+
body: JSON.stringify({ ...settings, client_operation_id: operationId }) },
|
|
299
262
|
),
|
|
300
263
|
}
|
|
301
264
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../../language/index.js'
|
|
1
2
|
import { t } from '../../language/index.js'
|
|
2
3
|
import { state } from '../../core/state.js'
|
|
3
4
|
import {
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
export async function applyScrollPreviews(updates, services) {
|
|
11
12
|
const changes = updates.map(({ source, params }) => {
|
|
12
13
|
const config = normalizeScrollPreviewConfig(params)
|
|
13
|
-
if (!config) throw new Error(
|
|
14
|
+
if (!config) throw new Error(errorT('features.editing.scrollPreview.invalidParameters'))
|
|
14
15
|
return {
|
|
15
16
|
source,
|
|
16
17
|
config,
|
|
@@ -120,7 +121,7 @@ export default {
|
|
|
120
121
|
submitLabel: 'features.editing.scrollPreview.apply',
|
|
121
122
|
async execute(params, item, services) {
|
|
122
123
|
const source = state.layoutItems.find((entry) => entry.id === item.sourceId)
|
|
123
|
-
if (!source) throw new Error(
|
|
124
|
+
if (!source) throw new Error(errorT('features.editing.scrollPreview.sourceMissing'))
|
|
124
125
|
await applyScrollPreview(source, params, services)
|
|
125
126
|
await services.replaceLocal(item, [])
|
|
126
127
|
services.setSelection([source.id])
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../../language/index.js'
|
|
1
2
|
import { t } from '../../language/index.js'
|
|
2
3
|
export default {
|
|
3
4
|
render: (ui) => ui.stack(ui.field('padToSameSize'), ui.field('anchor')),
|
|
@@ -41,7 +42,7 @@ export default {
|
|
|
41
42
|
body: { id: item.sourceId, anchor: p.anchor, pad_to_same_size: p.padToSameSize },
|
|
42
43
|
checkResult(result) {
|
|
43
44
|
if ((result.images || []).length < 2)
|
|
44
|
-
throw new Error(
|
|
45
|
+
throw new Error(errorT('features.editing.imageSplitting.text005'))
|
|
45
46
|
},
|
|
46
47
|
}
|
|
47
48
|
},
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { t } from '../../language/index.js'
|
|
1
|
+
import { t, errorT } from '../../language/index.js'
|
|
2
2
|
import { state } from '../../core/state.js'
|
|
3
3
|
|
|
4
4
|
export async function sourceImageParameters(source, services) {
|
|
5
5
|
const dimensions = await new Promise((resolve, reject) => {
|
|
6
6
|
const image = new Image()
|
|
7
7
|
image.onload = () => resolve({ w: image.naturalWidth, h: image.naturalHeight })
|
|
8
|
-
image.onerror = () => reject(new Error(
|
|
8
|
+
image.onerror = () => reject(new Error(errorT('features.editing.imageProcessing.text009')))
|
|
9
9
|
image.src = services.imageUrl(source.id)
|
|
10
10
|
})
|
|
11
11
|
state.imageDims[source.id] = dimensions
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { frameSheetPreviewMeta } from '../../elements/image.js'
|
|
2
2
|
import { ensureFeatureState, subscribeFeatureState } from '../../core/feature-state.js'
|
|
3
|
-
import { t } from '../../language/index.js'
|
|
3
|
+
import { t, errorT, errorText } from '../../language/index.js'
|
|
4
4
|
import { state } from '../../core/state.js'
|
|
5
5
|
import { normalizeFrameEditor, frameEditorParentIds } from './frame-model.js'
|
|
6
6
|
|
|
@@ -547,7 +547,7 @@ function createFrameEditingTool(context) {
|
|
|
547
547
|
const assetId = String(reference.id || '').trim()
|
|
548
548
|
const image = state.images.find((candidate) => candidate.id === assetId)
|
|
549
549
|
if (!image || image.kind !== 'image') {
|
|
550
|
-
item.error =
|
|
550
|
+
item.error = errorT('features.frameEditor.frameEditing.text005')
|
|
551
551
|
renderCanvasItem(editorId)
|
|
552
552
|
return
|
|
553
553
|
}
|
|
@@ -555,7 +555,7 @@ function createFrameEditingTool(context) {
|
|
|
555
555
|
if (editor.rows < 64) item.rows = editor.rows + 1
|
|
556
556
|
else if (editor.columns < 64) item.columns = editor.columns + 1
|
|
557
557
|
else {
|
|
558
|
-
item.error =
|
|
558
|
+
item.error = errorT('features.frameEditor.frameEditing.text006')
|
|
559
559
|
renderCanvasItem(editorId)
|
|
560
560
|
return
|
|
561
561
|
}
|
|
@@ -566,7 +566,7 @@ function createFrameEditingTool(context) {
|
|
|
566
566
|
item.error = null
|
|
567
567
|
await commitFrameEditorChange(editorId)
|
|
568
568
|
} catch {
|
|
569
|
-
item.error =
|
|
569
|
+
item.error = errorT('features.frameEditor.frameEditing.text007')
|
|
570
570
|
renderCanvasItem(editorId)
|
|
571
571
|
}
|
|
572
572
|
}
|
|
@@ -679,7 +679,7 @@ function createFrameEditingTool(context) {
|
|
|
679
679
|
|
|
680
680
|
const hint = document.createElement('p')
|
|
681
681
|
hint.className = `frame-editor-hint${node.error ? ' is-error' : ''}`
|
|
682
|
-
hint.textContent = node.error || t('features.frameEditor.frameEditing.text018')
|
|
682
|
+
hint.textContent = errorText(node.error) || t('features.frameEditor.frameEditing.text018')
|
|
683
683
|
|
|
684
684
|
workspace.addEventListener('pointerdown', (event) => event.stopPropagation())
|
|
685
685
|
container.append(workspace, hint)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorT } from '../../language/index.js'
|
|
1
2
|
import { t } from '../../language/index.js'
|
|
2
3
|
import { state } from '../../core/state.js'
|
|
3
4
|
import {
|
|
@@ -10,9 +11,10 @@ import {
|
|
|
10
11
|
|
|
11
12
|
export async function detectFramePreviewCount(imageId, rows, columns, imageUrl) {
|
|
12
13
|
const image = new Image()
|
|
14
|
+
image.crossOrigin = 'anonymous'
|
|
13
15
|
await new Promise((resolve, reject) => {
|
|
14
16
|
image.onload = resolve
|
|
15
|
-
image.onerror = () => reject(new Error(
|
|
17
|
+
image.onerror = () => reject(new Error(errorT('features.frameEditor.frameEditingDialogs.text007')))
|
|
16
18
|
image.src = imageUrl(imageId)
|
|
17
19
|
})
|
|
18
20
|
const canvas = document.createElement('canvas')
|
|
@@ -140,7 +142,7 @@ export default {
|
|
|
140
142
|
submitLabel: 'html.text072',
|
|
141
143
|
async execute(p, item, services) {
|
|
142
144
|
const source = state.layoutItems.find((entry) => entry.id === item.sourceId)
|
|
143
|
-
if (!source) throw new Error(
|
|
145
|
+
if (!source) throw new Error(errorT('inspector.framePreview.text011'))
|
|
144
146
|
await applyFramePreview(source, p, services)
|
|
145
147
|
await services.replaceLocal(item, [])
|
|
146
148
|
services.setSelection([source.id])
|