93 lines
8.5 KiB
HTML
93 lines
8.5 KiB
HTML
<!doctype html>
|
|
<meta charset="utf-8">
|
|
<title>Chrome Sketcher interactive editor harness</title>
|
|
<div id="viewport" tabindex="0" style="position:relative;width:200px;height:100px">
|
|
<button id="drag-handle" data-geometry-id="line" data-point="end" aria-label="Line end point" style="position:absolute;left:50px;top:10px;width:16px;height:16px"></button>
|
|
</div>
|
|
<pre id="result">running</pre>
|
|
<script type="module">
|
|
import { applySketchAutoConstraints, createSketch, dragSketchPoint, extendSketchLine, replaySketchEditorEvents, setSketchConstruction, SketchEditorInteractionSession, splitSketchLine, suggestSketchAutoConstraints, trimSketchLine } from '/src/facade/sketcher.ts'
|
|
|
|
const output = document.querySelector('#result')
|
|
const report = { schemaVersion: 1, browserId: 'chrome', crossOriginIsolated: globalThis.crossOriginIsolated === true, status: 'running' }
|
|
const base = createSketch('chrome-editor', [
|
|
{ id: 'line-a', type: 'line', start: { x: 0, y: 0 }, end: { x: 5, y: 0.0001 } },
|
|
{ id: 'line-b', type: 'line', start: { x: 5.0001, y: 0 }, end: { x: 5, y: 4 } },
|
|
])
|
|
const line = createSketch('chrome-editor-line', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 10, y: 0 } }])
|
|
const dragLine = createSketch('chrome-editor-drag-line', [{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 5, y: 1 } }], [{ id: 'horizontal', type: 'horizontal', geometryId: 'line' }])
|
|
const trimLine = createSketch('chrome-editor-trim-line', [
|
|
{ id: 'line', type: 'line', start: { x: 0, y: 0 }, end: { x: 10, y: 0 } },
|
|
{ id: 'cutter', type: 'line', start: { x: 7, y: -2 }, end: { x: 7, y: 2 } },
|
|
])
|
|
const endpoint = (snapshot, id = 'line') => {
|
|
const geometry = snapshot.geometry.find((candidate) => candidate.id === id)
|
|
return geometry?.type === 'line' ? geometry : null
|
|
}
|
|
try {
|
|
const dragged = dragSketchPoint(dragLine, { geometryId: 'line', point: 'end' }, { x: 8, y: 2 })
|
|
const split = splitSketchLine(line, 'line', { x: 4, y: 0 }, 'line-2')
|
|
const extended = extendSketchLine(line, 'line', 'end', { x: 15, y: 0 })
|
|
const trimmed = trimSketchLine(trimLine, 'line', 'end', { x: 7, y: 0 })
|
|
const construction = setSketchConstruction(line, 'line', true)
|
|
const suggestions = suggestSketchAutoConstraints(base, 'line-a', 0.001)
|
|
const auto = applySketchAutoConstraints(base, suggestions)
|
|
const replay = replaySketchEditorEvents(line, [
|
|
{ type: 'drag', point: { geometryId: 'line', point: 'end' }, target: { x: 8, y: 0 } },
|
|
{ type: 'undo' },
|
|
{ type: 'redo' },
|
|
{ type: 'trim', geometryId: 'line', endpoint: 'end', target: { x: 6, y: 0 } },
|
|
])
|
|
const interaction = new SketchEditorInteractionSession(dragLine)
|
|
interaction.setTool('drag')
|
|
const viewport = document.querySelector('#viewport')
|
|
const dragHandle = document.querySelector('#drag-handle')
|
|
const nativeEvents = { pointer: 0, keyboard: 0 }
|
|
const pointerPosition = (event) => ({ x: event.clientX / 10, y: event.clientY / 10 })
|
|
viewport.addEventListener('pointerdown', (event) => {
|
|
nativeEvents.pointer += 1
|
|
const target = event.target.closest?.('[data-geometry-id]')
|
|
interaction.pointerDown({ position: pointerPosition(event), target: target ? { geometryId: target.dataset.geometryId, point: target.dataset.point } : undefined })
|
|
})
|
|
viewport.addEventListener('pointerup', (event) => { nativeEvents.pointer += 1; interaction.pointerUp({ position: pointerPosition(event) }) })
|
|
viewport.addEventListener('keydown', (event) => { nativeEvents.keyboard += 1; if (interaction.keyDown(event)) event.preventDefault() })
|
|
dragHandle.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: 50, clientY: 10, pointerId: 1 }))
|
|
viewport.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, clientX: 80, clientY: 20, pointerId: 1 }))
|
|
viewport.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'z', ctrlKey: true }))
|
|
viewport.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'y', ctrlKey: true }))
|
|
interaction.setTool('drag')
|
|
dragHandle.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: 80, clientY: 20, pointerId: 2 }))
|
|
viewport.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: 'Escape' }))
|
|
const markerDirectory = await navigator.storage.getDirectory()
|
|
const markerName = 'sketcher-editor-chrome-marker.json'
|
|
const marker = await markerDirectory.getFileHandle(markerName, { create: true })
|
|
const writable = await marker.createWritable()
|
|
await writable.write(JSON.stringify({ suite: 'SK-09', operations: 9 }))
|
|
await writable.close()
|
|
const markerPayload = JSON.parse(await (await marker.getFile()).text())
|
|
await markerDirectory.removeEntry(markerName)
|
|
let markerRemoved = false
|
|
try { await markerDirectory.getFileHandle(markerName) } catch (error) { markerRemoved = error?.name === 'NotFoundError' }
|
|
report.operations = {
|
|
drag: { start: endpoint(dragged.snapshot)?.start ?? null, end: endpoint(dragged.snapshot)?.end ?? null, sourceUnchanged: endpoint(dragLine)?.end?.x === 5 && endpoint(dragLine)?.end?.y === 1 },
|
|
split: { ids: split.geometry.map((geometry) => geometry.id), endpoints: split.geometry.filter((geometry) => geometry.type === 'line').map((geometry) => [geometry.start, geometry.end]), constraintTypes: split.constraints.map((constraint) => constraint.type) },
|
|
extend: { end: endpoint(extended)?.end ?? null, sourceUnchanged: endpoint(line)?.end?.x === 10 },
|
|
trim: { end: endpoint(trimmed)?.end ?? null, constraintTypes: trimmed.constraints.map((constraint) => constraint.type), sourceUnchanged: endpoint(trimLine)?.end?.x === 10 },
|
|
construction: { enabled: construction.geometry[0]?.construction === true, sourceUnchanged: line.geometry[0]?.construction !== true },
|
|
autoConstraint: { suggestionReasons: suggestions.map((suggestion) => suggestion.reason), appliedIds: auto.constraints.map((constraint) => constraint.id), sourceUnchanged: base.constraints.length === 0 },
|
|
replay: { applied: replay.applied, undone: replay.undone, redone: replay.redone, finalEnd: endpoint(replay.snapshot)?.end ?? null },
|
|
}
|
|
const interactionLine = endpoint(interaction.getSnapshot())
|
|
const interactionState = interaction.getState()
|
|
report.interactions = { nativeEvents, start: interactionLine?.start ?? null, end: interactionLine?.end ?? null, tool: interactionState.tool, pointerActive: interactionState.pointerActive, applied: interactionState.applied, undone: interactionState.undone, redone: interactionState.redone }
|
|
report.opfs = { markerSuite: markerPayload.suite, markerOperations: markerPayload.operations, markerRemoved }
|
|
report.status = report.crossOriginIsolated && report.operations.drag.start?.y === 2 && report.operations.drag.end?.x === 8 && report.operations.drag.end?.y === 2 && report.operations.drag.sourceUnchanged && JSON.stringify(report.operations.split.ids) === JSON.stringify(['line', 'line-2']) && JSON.stringify(report.operations.split.constraintTypes) === JSON.stringify(['coincident']) && report.operations.extend.end?.x === 15 && report.operations.extend.sourceUnchanged && report.operations.trim.end?.x === 7 && JSON.stringify(report.operations.trim.constraintTypes) === JSON.stringify(['pointOnObject']) && report.operations.trim.sourceUnchanged && report.operations.construction.enabled && report.operations.construction.sourceUnchanged && JSON.stringify(report.operations.autoConstraint.suggestionReasons) === JSON.stringify(['coincident', 'horizontal']) && report.operations.autoConstraint.appliedIds.length === 2 && report.operations.autoConstraint.sourceUnchanged && report.operations.replay.applied === 2 && report.operations.replay.undone === 1 && report.operations.replay.redone === 1 && report.operations.replay.finalEnd?.x === 6 && report.interactions.nativeEvents.pointer === 3 && report.interactions.nativeEvents.keyboard === 3 && report.interactions.start?.y === 2 && report.interactions.end?.x === 8 && report.interactions.end?.y === 2 && report.interactions.tool === 'select' && report.interactions.pointerActive === false && report.interactions.applied === 1 && report.interactions.undone === 1 && report.interactions.redone === 1 && report.opfs.markerSuite === 'SK-09' && report.opfs.markerOperations === 9 && report.opfs.markerRemoved ? 'pass' : 'failed'
|
|
} catch (error) {
|
|
report.status = 'failed'
|
|
report.error = error instanceof Error ? error.message : String(error)
|
|
}
|
|
output.textContent = JSON.stringify(report, null, 2)
|
|
await fetch('/__chrome-sketcher-editor-report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
|
|
if (report.status !== 'pass') throw new Error(report.error || 'Chrome Sketcher editor harness failed.')
|
|
</script>
|