Release v0.14.0
This commit is contained in:
parent
f972efe229
commit
751195891f
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
## 0.14.0 - 2026-08-07
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **Core SDK:** Import scene graph types, geometry, coordinate, matrix, snapping, undo, and path helpers from `@open-pencil/scene-graph`; import `.pen` parsing from `@open-pencil/pen`; and import synchronous Kiwi decompression from `@open-pencil/kiwi` instead of the `@open-pencil/core` compatibility barrel.
|
||||
|
|
|
|||
2
desktop/Cargo.lock
generated
2
desktop/Cargo.lock
generated
|
|
@ -2939,7 +2939,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "open_pencil"
|
||||
version = "0.13.2"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"fix-path-env",
|
||||
"font-kit",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "open_pencil"
|
||||
version = "0.13.2"
|
||||
version = "0.14.0"
|
||||
description = "OpenPencil desktop app"
|
||||
authors = ["Danila Poyarkov"]
|
||||
edition = "2021"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "OpenPencil",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"identifier": "net.dannote.open-pencil",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run generate:tauri-menu && bun run dev",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "open-pencil-app",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/cli",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"imports": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/core",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -44,13 +44,13 @@ export function registerFigPopulationWorker(graph: SceneGraph, worker: Worker):
|
|||
emitTelemetry({ event: 'registered' })
|
||||
}
|
||||
|
||||
function isDevelopmentBuild(meta: { env?: { DEV?: boolean } }): boolean {
|
||||
return meta.env?.DEV ?? false
|
||||
function isDevelopmentBuild(env?: { DEV?: boolean }): boolean {
|
||||
return env?.DEV ?? false
|
||||
}
|
||||
|
||||
export function canUseFigPopulationWorker(graph: SceneGraph): boolean {
|
||||
return (
|
||||
isDevelopmentBuild(import.meta) &&
|
||||
isDevelopmentBuild(import.meta.env) &&
|
||||
populationWorkers.has(graph) &&
|
||||
getLazyFigImportContext(graph) !== undefined
|
||||
)
|
||||
|
|
@ -81,7 +81,10 @@ function createPopulationWorkerClient(graph: SceneGraph, worker: Worker): FigPop
|
|||
let disposed = false
|
||||
let applyingDelta = false
|
||||
const invalidate = () => {
|
||||
if (applyingDelta || stale) return
|
||||
// Layout recomputation (import-time or after a switch) is derived from the
|
||||
// same scene graph the worker deltas were built from; it must not count as
|
||||
// user divergence. Only real user edits invalidate the worker.
|
||||
if (applyingDelta || stale || graph.isApplyingLayout) return
|
||||
revision++
|
||||
stale = true
|
||||
emitTelemetry({ event: 'stale', reason: 'graph-mutation' })
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ import {
|
|||
} from './layout/yoga-helpers'
|
||||
|
||||
export function computeLayout(graph: SceneGraph, frameId: string): void {
|
||||
graph.withLayoutMutations(() => computeLayoutInternal(graph, frameId))
|
||||
}
|
||||
|
||||
function computeLayoutInternal(graph: SceneGraph, frameId: string): void {
|
||||
const frame = graph.getNode(frameId)
|
||||
if (!frame || frame.layoutMode === 'NONE') return
|
||||
|
||||
|
|
@ -47,7 +51,7 @@ export function computeLayout(graph: SceneGraph, frameId: string): void {
|
|||
? buildGridTree(graph, frame, rootDirection)
|
||||
: buildYogaTree(graph, frame, rootDirection)
|
||||
yogaRoot.calculateLayout(undefined, undefined, yogaDirection)
|
||||
applyYogaLayout(graph, frame, yogaRoot, computeLayout)
|
||||
applyYogaLayout(graph, frame, yogaRoot, computeLayoutInternal)
|
||||
freeYogaTree(yogaRoot)
|
||||
}
|
||||
function resolveComputedLayoutDirection(
|
||||
|
|
@ -60,12 +64,14 @@ function resolveComputedLayoutDirection(
|
|||
}
|
||||
|
||||
export function computeAllLayouts(graph: SceneGraph, scopeId?: string): void {
|
||||
const rootId = scopeId ?? graph.rootId
|
||||
const visited = new Set<string>()
|
||||
computeLayoutsBottomUp(graph, rootId, visited)
|
||||
if (applyEffectiveGeneratedTextLayout(graph, rootId)) {
|
||||
computeLayoutsBottomUp(graph, rootId, new Set())
|
||||
}
|
||||
graph.withLayoutMutations(() => {
|
||||
const rootId = scopeId ?? graph.rootId
|
||||
const visited = new Set<string>()
|
||||
computeLayoutsBottomUp(graph, rootId, visited)
|
||||
if (applyEffectiveGeneratedTextLayout(graph, rootId)) {
|
||||
computeLayoutsBottomUp(graph, rootId, new Set())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function computeLayoutsBottomUp(graph: SceneGraph, nodeId: string, visited: Set<string>): void {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/dom-css",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"imports": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/fig",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/kiwi",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/mcp",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"imports": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/pen",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/scene-graph",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ export class SceneGraph {
|
|||
private absPosCache = new Map<string, Vector>()
|
||||
private previewMutationDepth = 0
|
||||
private sourceMetadataPreservationDepth = 0
|
||||
private layoutMutationDepth = 0
|
||||
positionPreviewVersion = 0
|
||||
instanceIndex = new Map<string, Set<string>>()
|
||||
|
||||
|
|
@ -366,6 +367,17 @@ export class SceneGraph {
|
|||
this.sourceMetadataPreservationDepth--
|
||||
}
|
||||
}
|
||||
withLayoutMutations(fn: () => void): void {
|
||||
this.layoutMutationDepth++
|
||||
try {
|
||||
fn()
|
||||
} finally {
|
||||
this.layoutMutationDepth--
|
||||
}
|
||||
}
|
||||
get isApplyingLayout(): boolean {
|
||||
return this.layoutMutationDepth > 0
|
||||
}
|
||||
updateNodePositionPreview(id: string, x: number, y: number): void {
|
||||
this.updateNodePreview(id, { x, y })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@open-pencil/vue",
|
||||
"version": "0.13.2",
|
||||
"version": "0.14.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
|
|||
const editor = useEditorSetup()
|
||||
|
||||
test('populates a real lazy FIG page in the retained parse worker', async () => {
|
||||
test.setTimeout(90_000)
|
||||
await editor.page.evaluate(() => {
|
||||
const events: unknown[] = []
|
||||
Object.assign(window, { figPopulationWorkerEvents: events })
|
||||
|
|
@ -11,7 +12,7 @@ test('populates a real lazy FIG page in the retained parse worker', async () =>
|
|||
})
|
||||
})
|
||||
const openFile = editor.page.evaluate(() =>
|
||||
window.openPencil?.openFile?.('/tests/fixtures/gold-preview.fig')
|
||||
window.openPencil?.openFile?.('/tests/fixtures/material3.fig')
|
||||
)
|
||||
await openFile
|
||||
await editor.canvas.waitForRender()
|
||||
|
|
@ -23,18 +24,28 @@ test('populates a real lazy FIG page in the retained parse worker', async () =>
|
|||
})
|
||||
if (!targetPageId) throw new Error('Target page not found')
|
||||
|
||||
const populateEventCount = await editor.page.evaluate(
|
||||
() =>
|
||||
(Reflect.get(window, 'figPopulationWorkerEvents') as Array<{ event: string }>).filter(
|
||||
({ event }) => event === 'populate'
|
||||
).length
|
||||
)
|
||||
await editor.page.evaluate((pageId) => {
|
||||
const store = window.openPencil?.getStore?.()
|
||||
if (!store) throw new Error('OpenPencil store not initialized')
|
||||
return store.switchPage(pageId)
|
||||
}, targetPageId)
|
||||
await expect
|
||||
.poll(() =>
|
||||
editor.page.evaluate(() =>
|
||||
(Reflect.get(window, 'figPopulationWorkerEvents') as Array<{ event: string }>).some(
|
||||
({ event }) => event === 'populate'
|
||||
)
|
||||
)
|
||||
.poll(
|
||||
() =>
|
||||
editor.page.evaluate(
|
||||
(previousCount) =>
|
||||
(Reflect.get(window, 'figPopulationWorkerEvents') as Array<{ event: string }>).filter(
|
||||
({ event }) => event === 'populate'
|
||||
).length > previousCount,
|
||||
populateEventCount
|
||||
),
|
||||
{ timeout: 45_000 }
|
||||
)
|
||||
.toBe(true)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.6 KiB |
Loading…
Reference in a new issue