chore(lint): check duplicate type shapes across files
- Add a repo-wide TypeScript AST duplicate shape check to the quality gate - Extract shared named types for existing cross-file duplicates - Keep same-file oxlint coverage for editor feedback
This commit is contained in:
parent
2f8dfd883a
commit
f4b3453fbd
|
|
@ -16,7 +16,7 @@
|
||||||
"lint:structure": "oxlint -c oxlint.json vite.config.ts vite/ src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/ tests/ scripts/",
|
"lint:structure": "oxlint -c oxlint.json vite.config.ts vite/ src/ packages/core/src/ packages/vue/src/ packages/cli/src/ packages/mcp/src/ tests/ scripts/",
|
||||||
"format": "oxfmt --write .oxfmtrc.json vite.config.ts vite/ src/ packages/core/src/ packages/cli/src/ packages/mcp/src/ packages/vue/src/ tests scripts/",
|
"format": "oxfmt --write .oxfmtrc.json vite.config.ts vite/ src/ packages/core/src/ packages/cli/src/ packages/mcp/src/ packages/vue/src/ tests scripts/",
|
||||||
"format:check": "bun run format && status=$(git status --porcelain -uall) && test -z \"$status\" || (echo \"$status\" && exit 1)",
|
"format:check": "bun run format && status=$(git status --porcelain -uall) && test -z \"$status\" || (echo \"$status\" && exit 1)",
|
||||||
"check": "bun run build:packages && bun run lint && tsgo --noEmit && bun run check:vue && bun run check:i18n && bun run check:packages && bun run check:arch && bun run test:dupes",
|
"check": "bun run build:packages && bun run lint && tsgo --noEmit && bun run check:vue && bun run check:i18n && bun run check:packages && bun run check:arch && bun run test:type-shapes && bun run test:dupes",
|
||||||
"check:i18n": "bun scripts/check-locales.ts",
|
"check:i18n": "bun scripts/check-locales.ts",
|
||||||
"check:packages": "bun scripts/check-package-metadata.ts",
|
"check:packages": "bun scripts/check-package-metadata.ts",
|
||||||
"check:arch": "steiger .",
|
"check:arch": "steiger .",
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"figma:debug": "open -a Figma --args --remote-debugging-port=9222",
|
"figma:debug": "open -a Figma --args --remote-debugging-port=9222",
|
||||||
"test:unit": "bun test ./tests/engine",
|
"test:unit": "bun test ./tests/engine",
|
||||||
"test:coverage": "bun test --coverage ./tests/engine",
|
"test:coverage": "bun test --coverage ./tests/engine",
|
||||||
|
"test:type-shapes": "bun scripts/type-shapes.ts",
|
||||||
"test:dupes": "jscpd packages/core/src packages/cli/src src --min-lines 5 --min-tokens 50 --format typescript --threshold 0",
|
"test:dupes": "jscpd packages/core/src packages/cli/src src --min-lines 5 --min-tokens 50 --format typescript --threshold 0",
|
||||||
"test:packages": "bun scripts/check-package-metadata.ts && bun scripts/smoke-packages.ts",
|
"test:packages": "bun scripts/check-package-metadata.ts && bun scripts/smoke-packages.ts",
|
||||||
"build:packages": "bun --filter @open-pencil/core build && bun --filter @open-pencil/vue build && bun --filter @open-pencil/mcp build && bun --filter @open-pencil/cli build",
|
"build:packages": "bun --filter @open-pencil/core build && bun --filter @open-pencil/vue build && bun --filter @open-pencil/mcp build && bun --filter @open-pencil/cli build",
|
||||||
|
|
|
||||||
8
packages/core/src/color/analysis.ts
Normal file
8
packages/core/src/color/analysis.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import type { Color } from '#core/types'
|
||||||
|
|
||||||
|
export interface ColorUsageEntry {
|
||||||
|
hex: string
|
||||||
|
color: Color
|
||||||
|
count: number
|
||||||
|
variableName: string | null
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { transform } from 'sucrase'
|
import { transform } from 'sucrase'
|
||||||
|
|
||||||
|
import type { RenderOptions as RenderJSXOptions } from '#core/design-jsx/types'
|
||||||
import type { SceneGraph } from '#core/scene-graph'
|
import type { SceneGraph } from '#core/scene-graph'
|
||||||
|
|
||||||
import * as React from './mini-react'
|
import * as React from './mini-react'
|
||||||
|
|
@ -164,12 +165,6 @@ export function buildComponent(jsxString: string): React.ComponentType {
|
||||||
return new Function('React', code)(React) as React.ComponentType
|
return new Function('React', code)(React) as React.ComponentType
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RenderJSXOptions {
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
parentId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render a JSX string into the scene graph.
|
* Render a JSX string into the scene graph.
|
||||||
* Works in both Node/Bun and the browser.
|
* Works in both Node/Bun and the browser.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { parseColor } from '#core/color'
|
import { parseColor } from '#core/color'
|
||||||
|
import type { RenderOptions } from '#core/design-jsx/types'
|
||||||
import { fetchIcons } from '#core/icons'
|
import { fetchIcons } from '#core/icons'
|
||||||
import { createIconFromPaths } from '#core/icons/render'
|
import { createIconFromPaths } from '#core/icons/render'
|
||||||
import { computeAllLayouts } from '#core/layout'
|
import { computeAllLayouts } from '#core/layout'
|
||||||
|
|
@ -39,12 +40,6 @@ const TYPE_MAP: Partial<Record<string, NodeType>> = {
|
||||||
h6: 'TEXT'
|
h6: 'TEXT'
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RenderOptions {
|
|
||||||
x?: number
|
|
||||||
y?: number
|
|
||||||
parentId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RenderResult {
|
export interface RenderResult {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
|
|
|
||||||
5
packages/core/src/design-jsx/types.ts
Normal file
5
packages/core/src/design-jsx/types.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface RenderOptions {
|
||||||
|
x?: number
|
||||||
|
y?: number
|
||||||
|
parentId?: string
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { orderBy, sortBy } from 'es-toolkit/array'
|
import { orderBy, sortBy } from 'es-toolkit/array'
|
||||||
|
|
||||||
import { colorToHex, colorDistance as colorDist } from '#core/color'
|
import { colorToHex, colorDistance as colorDist } from '#core/color'
|
||||||
|
import type { ColorUsageEntry } from '#core/color/analysis'
|
||||||
import type { SceneGraph, SceneNode } from '#core/scene-graph'
|
import type { SceneGraph, SceneNode } from '#core/scene-graph'
|
||||||
import type { Color } from '#core/types'
|
import type { Color } from '#core/types'
|
||||||
|
|
||||||
|
|
@ -13,12 +14,7 @@ export interface AnalyzeColorsArgs {
|
||||||
similar?: boolean
|
similar?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColorInfo {
|
type ColorInfo = ColorUsageEntry
|
||||||
hex: string
|
|
||||||
color: Color
|
|
||||||
count: number
|
|
||||||
variableName: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ColorCluster {
|
interface ColorCluster {
|
||||||
colors: ColorInfo[]
|
colors: ColorInfo[]
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
import { orderBy } from 'es-toolkit/array'
|
import { orderBy } from 'es-toolkit/array'
|
||||||
|
|
||||||
import { colorDistance, colorToHex } from '#core/color'
|
import { colorDistance, colorToHex } from '#core/color'
|
||||||
|
import type { ColorUsageEntry } from '#core/color/analysis'
|
||||||
import { defineTool } from '#core/tools/schema'
|
import { defineTool } from '#core/tools/schema'
|
||||||
import type { Color } from '#core/types'
|
import type { Color } from '#core/types'
|
||||||
|
|
||||||
interface ColorEntry {
|
type ColorEntry = ColorUsageEntry
|
||||||
hex: string
|
|
||||||
color: Color
|
|
||||||
count: number
|
|
||||||
variableName: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
function trackColor(colorMap: Map<string, ColorEntry>, color: Color, variableName: string | null) {
|
function trackColor(colorMap: Map<string, ColorEntry>, color: Color, variableName: string | null) {
|
||||||
const hex = colorToHex(color)
|
const hex = colorToHex(color)
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,13 @@ import { randomUUID } from 'node:crypto'
|
||||||
import type { WebSocket } from 'ws'
|
import type { WebSocket } from 'ws'
|
||||||
|
|
||||||
import type { RpcJsonObject } from '#mcp/json'
|
import type { RpcJsonObject } from '#mcp/json'
|
||||||
|
import type { PendingRequest } from '#mcp/rpc-types'
|
||||||
|
|
||||||
const RPC_TIMEOUT = 30_000
|
const RPC_TIMEOUT = 30_000
|
||||||
|
|
||||||
const APP_NOT_CONNECTED_MESSAGE =
|
const APP_NOT_CONNECTED_MESSAGE =
|
||||||
'OpenPencil app is not connected. STOP and tell the user: "The OpenPencil desktop app is not running, no document is open, or the desktop app is connected to a different MCP server. Please start OpenPencil, open a document, and try again." Do NOT attempt to start the app yourself or retry automatically.'
|
'OpenPencil app is not connected. STOP and tell the user: "The OpenPencil desktop app is not running, no document is open, or the desktop app is connected to a different MCP server. Please start OpenPencil, open a document, and try again." Do NOT attempt to start the app yourself or retry automatically.'
|
||||||
|
|
||||||
type PendingRequest = {
|
|
||||||
resolve: (value: unknown) => void
|
|
||||||
reject: (error: Error) => void
|
|
||||||
timer: ReturnType<typeof setTimeout>
|
|
||||||
}
|
|
||||||
|
|
||||||
type BrowserRpcBridgeOptions = {
|
type BrowserRpcBridgeOptions = {
|
||||||
authToken: string | null
|
authToken: string | null
|
||||||
onConnectionChange: () => void
|
onConnectionChange: () => void
|
||||||
|
|
|
||||||
5
packages/mcp/src/rpc-types.ts
Normal file
5
packages/mcp/src/rpc-types.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type PendingRequest = {
|
||||||
|
resolve: (value: unknown) => void
|
||||||
|
reject: (error: Error) => void
|
||||||
|
timer: ReturnType<typeof setTimeout>
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import { WebSocket } from 'ws'
|
import { WebSocket } from 'ws'
|
||||||
|
|
||||||
type PendingRequest = {
|
import type { PendingRequest } from '#mcp/rpc-types'
|
||||||
resolve: (value: unknown) => void
|
|
||||||
reject: (error: Error) => void
|
|
||||||
timer: ReturnType<typeof setTimeout>
|
|
||||||
}
|
|
||||||
|
|
||||||
type StdioRpcBridgeOptions = {
|
type StdioRpcBridgeOptions = {
|
||||||
wsUrl: string
|
wsUrl: string
|
||||||
|
|
|
||||||
1
packages/vue/src/shared/input/drag-original.ts
Normal file
1
packages/vue/src/shared/input/drag-original.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export type DragOriginal = { x: number; y: number; parentId: string }
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Editor } from '@open-pencil/core/editor'
|
import type { Editor } from '@open-pencil/core/editor'
|
||||||
|
|
||||||
import { useI18n } from '#vue/i18n/useI18n.js'
|
import { useI18n } from '#vue/i18n/useI18n.js'
|
||||||
|
import type { DragOriginal } from '#vue/shared/input/drag-original'
|
||||||
import type { DragState } from '#vue/shared/input/types'
|
import type { DragState } from '#vue/shared/input/types'
|
||||||
|
|
||||||
type DragOriginal = { x: number; y: number; parentId: string }
|
|
||||||
|
|
||||||
export function duplicateAndDrag(
|
export function duplicateAndDrag(
|
||||||
cx: number,
|
cx: number,
|
||||||
cy: number,
|
cy: number,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import type { Editor } from '@open-pencil/core/editor'
|
import type { Editor } from '@open-pencil/core/editor'
|
||||||
|
|
||||||
|
import type { DragOriginal as MoveOriginal } from '#vue/shared/input/drag-original'
|
||||||
import { duplicateAndDrag } from '#vue/shared/input/duplicate-drag'
|
import { duplicateAndDrag } from '#vue/shared/input/duplicate-drag'
|
||||||
import type { DragState } from '#vue/shared/input/types'
|
import type { DragState } from '#vue/shared/input/types'
|
||||||
|
|
||||||
type MoveOriginal = { x: number; y: number; parentId: string }
|
|
||||||
|
|
||||||
export function selectionIsLocked(editor: Editor) {
|
export function selectionIsLocked(editor: Editor) {
|
||||||
return [...editor.state.selectedIds].every((id) => editor.graph.getNode(id)?.locked)
|
return [...editor.state.selectedIds].every((id) => editor.graph.getNode(id)?.locked)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
123
scripts/type-shapes.ts
Normal file
123
scripts/type-shapes.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
import ts from 'typescript'
|
||||||
|
|
||||||
|
const roots = [
|
||||||
|
'src',
|
||||||
|
'packages/core/src',
|
||||||
|
'packages/vue/src',
|
||||||
|
'packages/cli/src',
|
||||||
|
'packages/mcp/src',
|
||||||
|
'tests',
|
||||||
|
'scripts'
|
||||||
|
]
|
||||||
|
|
||||||
|
type ShapeLocation = { file: string; line: number; name: string }
|
||||||
|
|
||||||
|
const files: string[] = []
|
||||||
|
for (const root of roots) {
|
||||||
|
for await (const path of new Bun.Glob('**/*.{ts,tsx}').scan(root)) {
|
||||||
|
if (path.endsWith('.d.ts')) continue
|
||||||
|
files.push(`${root}/${path}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function entityNameText(name: ts.EntityName): string {
|
||||||
|
return ts.isIdentifier(name) ? name.text : `${entityNameText(name.left)}.${name.right.text}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function literalText(node: ts.LiteralTypeNode) {
|
||||||
|
const literal = node.literal
|
||||||
|
if (ts.isStringLiteral(literal)) return `string:${literal.text}`
|
||||||
|
if (ts.isNumericLiteral(literal)) return `number:${literal.text}`
|
||||||
|
if (literal.kind === ts.SyntaxKind.TrueKeyword) return 'boolean:true'
|
||||||
|
if (literal.kind === ts.SyntaxKind.FalseKeyword) return 'boolean:false'
|
||||||
|
return ts.SyntaxKind[literal.kind]
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeText(node: ts.TypeNode | undefined): string {
|
||||||
|
if (!node) return 'unknown'
|
||||||
|
if (ts.isTypeReferenceNode(node)) {
|
||||||
|
return `ref:${entityNameText(node.typeName)}<${(node.typeArguments ?? []).map(typeText).join(',')}>`
|
||||||
|
}
|
||||||
|
if (ts.isArrayTypeNode(node)) return `array<${typeText(node.elementType)}>`
|
||||||
|
if (ts.isUnionTypeNode(node)) return `union<${node.types.map(typeText).sort().join('|')}>`
|
||||||
|
if (ts.isTypeLiteralNode(node)) return `object{${membersText(node.members)}}`
|
||||||
|
if (ts.isParenthesizedTypeNode(node)) return typeText(node.type)
|
||||||
|
if (ts.isLiteralTypeNode(node)) return `literal:${literalText(node)}`
|
||||||
|
return ts.SyntaxKind[node.kind]
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyText(name: ts.PropertyName): string | null {
|
||||||
|
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
|
||||||
|
return name.text
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function memberText(member: ts.TypeElement): string | null {
|
||||||
|
if (ts.isPropertySignature(member) && member.name) {
|
||||||
|
const key = keyText(member.name)
|
||||||
|
if (!key) return null
|
||||||
|
return `prop:${key}${member.questionToken ? '?' : ''}:${typeText(member.type)}`
|
||||||
|
}
|
||||||
|
if (ts.isIndexSignatureDeclaration(member)) {
|
||||||
|
return `index:${member.parameters.map((param) => typeText(param.type)).join(',')}:${typeText(member.type)}`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function membersText(members: ts.NodeArray<ts.TypeElement>) {
|
||||||
|
return members
|
||||||
|
.map(memberText)
|
||||||
|
.filter((member): member is string => member !== null)
|
||||||
|
.sort()
|
||||||
|
.join(';')
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectShape(node: ts.Node): { name: string; shape: string } | null {
|
||||||
|
if (ts.isInterfaceDeclaration(node))
|
||||||
|
return { name: node.name.text, shape: membersText(node.members) }
|
||||||
|
if (ts.isTypeAliasDeclaration(node) && ts.isTypeLiteralNode(node.type)) {
|
||||||
|
return { name: node.name.text, shape: membersText(node.type.members) }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const shapes = new Map<string, ShapeLocation[]>()
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const source = await Bun.file(file).text()
|
||||||
|
const kind = file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
||||||
|
const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, kind)
|
||||||
|
|
||||||
|
function visit(node: ts.Node) {
|
||||||
|
const collected = collectShape(node)
|
||||||
|
if (collected) {
|
||||||
|
const memberCount = collected.shape.split(';').filter(Boolean).length
|
||||||
|
if (memberCount >= 2) {
|
||||||
|
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile))
|
||||||
|
const locations = shapes.get(collected.shape) ?? []
|
||||||
|
locations.push({ file, line: position.line + 1, name: collected.name })
|
||||||
|
shapes.set(collected.shape, locations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit)
|
||||||
|
}
|
||||||
|
|
||||||
|
visit(sourceFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
let duplicates = 0
|
||||||
|
for (const locations of shapes.values()) {
|
||||||
|
if (locations.length < 2) continue
|
||||||
|
duplicates++
|
||||||
|
console.error('\nDuplicate object type shape:')
|
||||||
|
for (const location of locations) {
|
||||||
|
console.error(` ${location.file}:${location.line} ${location.name}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicates > 0) {
|
||||||
|
console.error(`\nFound ${duplicates} duplicate object type shape${duplicates === 1 ? '' : 's'}.`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('No duplicate object type shapes found.')
|
||||||
|
|
@ -9,12 +9,7 @@ import {
|
||||||
getExportOptions,
|
getExportOptions,
|
||||||
saveExportedFile
|
saveExportedFile
|
||||||
} from '@/app/document/export/files'
|
} from '@/app/document/export/files'
|
||||||
|
import type { ExportOptions } from '@/app/document/export/types'
|
||||||
type ExportOptions = {
|
|
||||||
scale?: number
|
|
||||||
quality?: number
|
|
||||||
jsxFormat?: 'openpencil' | 'tailwind'
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloadBlob = (data: Uint8Array, filename: string, mime: string) => void
|
type DownloadBlob = (data: Uint8Array, filename: string, mime: string) => void
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,9 @@ import type {
|
||||||
import { renderNodesToImage } from '@open-pencil/core/io/formats/raster'
|
import { renderNodesToImage } from '@open-pencil/core/io/formats/raster'
|
||||||
import type { SceneGraph } from '@open-pencil/core/scene-graph'
|
import type { SceneGraph } from '@open-pencil/core/scene-graph'
|
||||||
|
|
||||||
|
import type { ExportOptions } from '@/app/document/export/types'
|
||||||
import { isTauri } from '@/app/tauri/env'
|
import { isTauri } from '@/app/tauri/env'
|
||||||
|
|
||||||
type ExportOptions = {
|
|
||||||
scale?: number
|
|
||||||
quality?: number
|
|
||||||
jsxFormat?: 'openpencil' | 'tailwind'
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExportData = string | ArrayBuffer | Uint8Array
|
type ExportData = string | ArrayBuffer | Uint8Array
|
||||||
|
|
||||||
type DownloadBlob = (data: Uint8Array, filename: string, mime: string) => void
|
type DownloadBlob = (data: Uint8Array, filename: string, mime: string) => void
|
||||||
|
|
|
||||||
5
src/app/document/export/types.ts
Normal file
5
src/app/document/export/types.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export type ExportOptions = {
|
||||||
|
scale?: number
|
||||||
|
quality?: number
|
||||||
|
jsxFormat?: 'openpencil' | 'tailwind'
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
|
import type { ViewportSize } from '@/app/document/io/types'
|
||||||
|
|
||||||
export function yieldToUI(): Promise<void> {
|
export function yieldToUI(): Promise<void> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
requestAnimationFrame(() => resolve())
|
requestAnimationFrame(() => resolve())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewportSize = { width: number; height: number }
|
|
||||||
|
|
||||||
type ViewportEditor = {
|
type ViewportEditor = {
|
||||||
zoomToFit: () => void
|
zoomToFit: () => void
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { prefetchFigmaSchema } from '@open-pencil/core/kiwi'
|
||||||
import { createDocumentViewportActions, downloadBlob } from '@/app/document/io/browser'
|
import { createDocumentViewportActions, downloadBlob } from '@/app/document/io/browser'
|
||||||
import { createOpenActions, createReloadActions } from '@/app/document/io/read'
|
import { createOpenActions, createReloadActions } from '@/app/document/io/read'
|
||||||
import { createDocumentSourceActions, createDocumentSourceState } from '@/app/document/io/source'
|
import { createDocumentSourceActions, createDocumentSourceState } from '@/app/document/io/source'
|
||||||
|
import type { ViewportSize } from '@/app/document/io/types'
|
||||||
import { createFileWatcher } from '@/app/document/io/watch'
|
import { createFileWatcher } from '@/app/document/io/watch'
|
||||||
|
|
||||||
type DocumentIOState = EditorState & {
|
type DocumentIOState = EditorState & {
|
||||||
|
|
@ -12,11 +13,6 @@ type DocumentIOState = EditorState & {
|
||||||
autosaveEnabled: boolean
|
autosaveEnabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type ViewportSize = {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocumentIOActions(
|
export function createDocumentIOActions(
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
state: DocumentIOState,
|
state: DocumentIOState,
|
||||||
|
|
|
||||||
1
src/app/document/io/types.ts
Normal file
1
src/app/document/io/types.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export type ViewportSize = { width: number; height: number }
|
||||||
|
|
@ -6,6 +6,7 @@ import type { SceneGraph } from '@open-pencil/core/scene-graph'
|
||||||
|
|
||||||
import { createDocumentExportActions } from '@/app/document/export'
|
import { createDocumentExportActions } from '@/app/document/export'
|
||||||
import { createDocumentIOActions } from '@/app/document/io'
|
import { createDocumentIOActions } from '@/app/document/io'
|
||||||
|
import type { ViewportSize } from '@/app/document/io/types'
|
||||||
import { createFlashActions } from '@/app/editor/flash'
|
import { createFlashActions } from '@/app/editor/flash'
|
||||||
import { createMobileClipboardActions } from '@/app/editor/mobile-clipboard'
|
import { createMobileClipboardActions } from '@/app/editor/mobile-clipboard'
|
||||||
import { createPenActions } from '@/app/editor/pen'
|
import { createPenActions } from '@/app/editor/pen'
|
||||||
|
|
@ -13,8 +14,6 @@ import { createProfilerActions } from '@/app/editor/profiler'
|
||||||
import type { AppEditorState } from '@/app/editor/session/types'
|
import type { AppEditorState } from '@/app/editor/session/types'
|
||||||
import { createVectorEditActions } from '@/app/editor/vector-edit'
|
import { createVectorEditActions } from '@/app/editor/vector-edit'
|
||||||
|
|
||||||
type ViewportSize = { width: number; height: number }
|
|
||||||
|
|
||||||
export function defineEditorStoreAccessors(store: object, editor: Editor) {
|
export function defineEditorStoreAccessors(store: object, editor: Editor) {
|
||||||
Object.defineProperties(store, {
|
Object.defineProperties(store, {
|
||||||
graph: {
|
graph: {
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,9 @@ import {
|
||||||
predownloadFallbackFonts,
|
predownloadFallbackFonts,
|
||||||
requestLocalFontAccess
|
requestLocalFontAccess
|
||||||
} from '@/app/editor/fonts'
|
} from '@/app/editor/fonts'
|
||||||
|
import type { DownloadedFontCacheSummary } from '@/app/editor/fonts/cache'
|
||||||
|
|
||||||
export interface FontCacheSummary {
|
type FontCacheSummary = DownloadedFontCacheSummary
|
||||||
count: number
|
|
||||||
byteLength: number
|
|
||||||
updatedAt: number | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FontSettingsActions {
|
export interface FontSettingsActions {
|
||||||
clearDownloadedFontCache: () => Promise<void>
|
clearDownloadedFontCache: () => Promise<void>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
import { computed, inject, provide, proxyRefs } from 'vue'
|
import { computed, inject, provide, proxyRefs } from 'vue'
|
||||||
import type { Component, InjectionKey, ShallowUnwrapRef } from 'vue'
|
import type { InjectionKey, ShallowUnwrapRef } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import IconFilePlus from '~icons/lucide/file-plus'
|
import IconFilePlus from '~icons/lucide/file-plus'
|
||||||
import IconFolderOpen from '~icons/lucide/folder-open'
|
import IconFolderOpen from '~icons/lucide/folder-open'
|
||||||
|
|
@ -15,12 +15,9 @@ import { useEditorStore } from '@/app/editor/active-store'
|
||||||
import { toolIcons } from '@/app/editor/icons'
|
import { toolIcons } from '@/app/editor/icons'
|
||||||
import { openFileDialog } from '@/app/shell/menu/use'
|
import { openFileDialog } from '@/app/shell/menu/use'
|
||||||
import { toast } from '@/app/shell/ui'
|
import { toast } from '@/app/shell/ui'
|
||||||
|
import type { ToolbarActionItem } from '@/components/Toolbar/types'
|
||||||
|
|
||||||
interface MenuAction {
|
type MenuAction = ToolbarActionItem
|
||||||
icon: Component
|
|
||||||
label: string
|
|
||||||
action: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMobileHudContext() {
|
function createMobileHudContext() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue