Files
Web_FreeCAD_Bitbybit/public/chrome-sketcher-editor-harness.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>