mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
220 lines
9.7 KiB
JavaScript
220 lines
9.7 KiB
JavaScript
import { makeWallDrawClickFixture, WALL_DRAW_CLICK_POINTS } from './fixtures/wall-draw-click.mjs';
|
|
|
|
export async function installWallDrawClickHarness(page) {
|
|
await page.evaluate(() => {
|
|
const card = window.__card;
|
|
if (!window.__wallDrawOriginals) {
|
|
window.__wallDrawOriginals = {
|
|
physical: card._checkSpacePhysicalGeometry.bind(card),
|
|
junction: card._junctionLimitViolations.bind(card),
|
|
save: card._saveConfigDebounced,
|
|
genericCommit: card._editorRuntime._commitPhysicalGeometry.bind(card._editorRuntime),
|
|
finalize: card._editorRuntime._finalizeWallChainPartitions.bind(card._editorRuntime),
|
|
};
|
|
card._editorRuntime._finalizeWallChainPartitions = (...args) => {
|
|
if (window.__wallDrawMetrics) window.__wallDrawMetrics.wallFinishBarriers += 1;
|
|
return window.__wallDrawOriginals.finalize(...args);
|
|
};
|
|
card._editorRuntime._commitPhysicalGeometry = (name, before, ...rest) => {
|
|
const metrics = window.__wallDrawMetrics;
|
|
if (metrics && name === card._t('history.wall_segment')) {
|
|
const finishCommit = rest[1] === false;
|
|
if (finishCommit) metrics.wallFinishCommits += 1;
|
|
else metrics.wallGenericFallbacks += 1;
|
|
if (!finishCommit && !metrics.fallbackSample) {
|
|
const space = card._serverCfg?.spaces?.find((item) => item.id === before?.spaceId);
|
|
const stable = (value) => JSON.stringify(value ?? null);
|
|
const arrays = [
|
|
'rooms', 'openings', 'walls', 'wall_segments', 'open_spans',
|
|
'partitions', 'wall_columns', 'decor',
|
|
];
|
|
metrics.fallbackSample = JSON.parse(JSON.stringify({
|
|
modelVersion: card._serverCfg?.model_version,
|
|
chainId: card._activeWallChainId,
|
|
activePartitionIds: card._activeWallChainPartitionIds,
|
|
beforePartitions: before?.partitions,
|
|
afterPartitions: space?.partitions,
|
|
beforeTransform: before?.plan_transform,
|
|
afterTransform: Object.fromEntries([
|
|
'plan_x', 'plan_y', 'plan_scale', 'plan_scale_x', 'plan_scale_y', 'plan_angle',
|
|
].filter((key) => space?.[key] !== undefined).map((key) => [key, space[key]])),
|
|
changedArrays: arrays.filter((key) =>
|
|
stable(before?.[key] ?? []) !== stable(space?.[key] ?? [])),
|
|
}));
|
|
}
|
|
}
|
|
return window.__wallDrawOriginals.genericCommit(name, before, ...rest);
|
|
};
|
|
card._checkSpacePhysicalGeometry = (config, ...rest) => {
|
|
const metrics = window.__wallDrawMetrics;
|
|
if (metrics) {
|
|
const local = config?.spaces?.length === 1;
|
|
metrics[local ? 'localPhysicalChecks' : 'fullSpacePhysicalChecks'] += 1;
|
|
if (local) {
|
|
const space = config.spaces[0] || {};
|
|
const count = ['wall_segments', 'partitions', 'wall_columns']
|
|
.reduce((sum, key) => sum + (space[key]?.length || 0), space.rooms?.length || 0);
|
|
metrics.localProofMaxObjects = Math.max(metrics.localProofMaxObjects, count);
|
|
}
|
|
}
|
|
return window.__wallDrawOriginals.physical(config, ...rest);
|
|
};
|
|
card._junctionLimitViolations = (config, spaceId, geometry, roomIds) => {
|
|
const metrics = window.__wallDrawMetrics;
|
|
if (metrics) {
|
|
metrics.junctionPasses += 1;
|
|
if (geometry) metrics.junctionArtifactPasses += 1;
|
|
}
|
|
return window.__wallDrawOriginals.junction(config, spaceId, geometry, roomIds);
|
|
};
|
|
const countedSave = () => {
|
|
if (window.__wallDrawMetrics) window.__wallDrawMetrics.configWrites += 1;
|
|
};
|
|
countedSave.cancel = () => {};
|
|
countedSave.flush = () => {};
|
|
card._saveConfigDebounced = countedSave;
|
|
}
|
|
});
|
|
}
|
|
|
|
export async function resetWallDrawClickHarness(page, remoteVariant = false) {
|
|
const fixture = makeWallDrawClickFixture(remoteVariant);
|
|
return page.evaluate(async (config) => {
|
|
const card = window.__card;
|
|
card._clearGeometryGesture();
|
|
card._serverCfg = JSON.parse(JSON.stringify(config));
|
|
card._space = 'edited'; card._layout = {};
|
|
card._cfgEpoch += 1; card._modelCache = null; card._wallUnionCache = null;
|
|
card._physicalBodiesCache = null; card._frame = null;
|
|
card._pendingPhysicalWrites.clear(); card._geometryHistory.clear();
|
|
card._activeWallChainId = null; card._activeWallChainPartitionIds = [];
|
|
card._path = []; card._wallChainSegmentCms = []; card._wallChainRedo = [];
|
|
card._toast = ''; card._mode = 'plan'; card._tool = 'draw'; card._drawWallField = '15';
|
|
window.__wallDrawMetrics = {
|
|
fullSpacePhysicalChecks: 0, localPhysicalChecks: 0,
|
|
junctionPasses: 0, junctionArtifactPasses: 0,
|
|
configWrites: 0, localProofMaxObjects: 0,
|
|
wallGenericFallbacks: 0, fallbackSample: null,
|
|
wallFinishCommits: 0,
|
|
wallFinishBarriers: 0,
|
|
};
|
|
card.requestUpdate(); await card.updateComplete;
|
|
return {
|
|
spaces: card._serverCfg.spaces.length,
|
|
rooms: card._curSpaceCfg.rooms.length,
|
|
positiveSegments: card._curSpaceCfg.wall_segments.filter((item) => item.cm > 0).length,
|
|
savedPartitions: card._curSpaceCfg.partitions.filter(
|
|
(item) => item.id.startsWith('saved-partition-'),
|
|
).length,
|
|
};
|
|
}, fixture);
|
|
}
|
|
|
|
export async function runWallDrawClickChain(page) {
|
|
return page.evaluate(async (normalizedPoints) => {
|
|
const card = window.__card;
|
|
const metrics = window.__wallDrawMetrics;
|
|
const stage = (card.shadowRoot || card.renderRoot).querySelector('.stage');
|
|
const eventAt = (normalized) => {
|
|
const point = normalized.map((value) => value * 1000);
|
|
const rect = stage.getBoundingClientRect();
|
|
const view = card._viewOr(card._baseVb());
|
|
return new MouseEvent('click', {
|
|
clientX: rect.left + ((point[0] - view.x) / view.w) * rect.width,
|
|
clientY: rect.top + ((point[1] - view.y) / view.h) * rect.height,
|
|
bubbles: true,
|
|
});
|
|
};
|
|
const snapshots = () => ({
|
|
...metrics, history: card._geometryHistory.size,
|
|
pathPoints: card._path.length,
|
|
});
|
|
const delta = (after, before) => Object.fromEntries(
|
|
Object.keys(after).map((key) => [key, after[key] - before[key]]),
|
|
);
|
|
|
|
const firstBefore = snapshots();
|
|
card._markupClick(eventAt(normalizedPoints[0]));
|
|
const first = delta(snapshots(), firstBefore);
|
|
const times = []; const clicks = [];
|
|
for (const normalized of normalizedPoints.slice(1)) {
|
|
const before = snapshots();
|
|
const started = performance.now();
|
|
card._markupClick(eventAt(normalized));
|
|
times.push(performance.now() - started);
|
|
clicks.push(delta(snapshots(), before));
|
|
}
|
|
const ids = [...card._activeWallChainPartitionIds];
|
|
const terminalBefore = snapshots();
|
|
card._activateMarkupTool('select');
|
|
const terminal = delta(snapshots(), terminalBefore);
|
|
await card.updateComplete;
|
|
return {
|
|
first, clicks, times, ids,
|
|
terminal,
|
|
terminalPartitionCount: card._curSpaceCfg.partitions?.length || 0,
|
|
activeCleared: !card._activeWallChainId
|
|
&& card._activeWallChainPartitionIds.length === 0 && card._path.length === 0,
|
|
metrics: { ...metrics },
|
|
};
|
|
}, WALL_DRAW_CLICK_POINTS);
|
|
}
|
|
|
|
/** A separate completed straight chain: unlike terminal clicks, finish owns
|
|
* the bounded lossless merge/reconcile barrier introduced by #477. */
|
|
export async function runWallDrawFinishProfile(page) {
|
|
return page.evaluate(async (normalizedPoints) => {
|
|
const card = window.__card;
|
|
const metrics = window.__wallDrawMetrics;
|
|
const stage = (card.shadowRoot || card.renderRoot).querySelector('.stage');
|
|
const eventAt = (normalized) => {
|
|
const point = normalized.map((value) => value * 1000);
|
|
const rect = stage.getBoundingClientRect();
|
|
const view = card._viewOr(card._baseVb());
|
|
return new MouseEvent('click', {
|
|
clientX: rect.left + ((point[0] - view.x) / view.w) * rect.width,
|
|
clientY: rect.top + ((point[1] - view.y) / view.h) * rect.height,
|
|
bubbles: true,
|
|
});
|
|
};
|
|
const snapshots = () => ({
|
|
fullSpacePhysicalChecks: metrics.fullSpacePhysicalChecks,
|
|
localPhysicalChecks: metrics.localPhysicalChecks,
|
|
junctionPasses: metrics.junctionPasses,
|
|
junctionArtifactPasses: metrics.junctionArtifactPasses,
|
|
configWrites: metrics.configWrites,
|
|
wallGenericFallbacks: metrics.wallGenericFallbacks,
|
|
wallFinishCommits: metrics.wallFinishCommits,
|
|
wallFinishBarriers: metrics.wallFinishBarriers,
|
|
history: card._geometryHistory.size,
|
|
});
|
|
const delta = (after, before) => Object.fromEntries(
|
|
Object.keys(after).map((key) => [key, after[key] - before[key]]),
|
|
);
|
|
|
|
for (const point of normalizedPoints) card._markupClick(eventAt(point));
|
|
const chainIds = [...card._activeWallChainPartitionIds];
|
|
const before = snapshots();
|
|
const started = performance.now();
|
|
card._activateMarkupTool('select');
|
|
const finishMs = performance.now() - started;
|
|
const terminal = delta(snapshots(), before);
|
|
card._previewAlignDialog(false);
|
|
const optimizeChanged = card._alignDialog?.changed;
|
|
card._alignDialog = null;
|
|
await card.updateComplete;
|
|
const surviving = (card._curSpaceCfg.partitions || []).filter(
|
|
(partition) => chainIds.includes(partition.id),
|
|
);
|
|
return {
|
|
finishMs, terminal, optimizeChanged,
|
|
chainIds, surviving,
|
|
terminalPartitionCount: card._curSpaceCfg.partitions?.length || 0,
|
|
activeCleared: !card._activeWallChainId
|
|
&& card._activeWallChainPartitionIds.length === 0 && card._path.length === 0,
|
|
metrics: { ...metrics },
|
|
};
|
|
}, [[152, 200], [176, 200], [200, 200], [224, 200]]
|
|
.map(([x, y]) => [x / 240, y / 240]));
|
|
}
|