refactor: improve selection parity and sanitize imports
This commit is contained in:
parent
e58b240778
commit
06868fed42
@ -297,12 +297,9 @@ export function Canvas(props: CanvasProps) {
|
|||||||
|
|
||||||
// If the node is already in the current selection, keep the selection as-is.
|
// If the node is already in the current selection, keep the selection as-is.
|
||||||
// This matches goView-ish multi-selection behavior (drag moves the whole selection).
|
// This matches goView-ish multi-selection behavior (drag moves the whole selection).
|
||||||
|
// Even if the clicked node is locked, we still allow starting a drag so other
|
||||||
|
// unlocked nodes in the selection can move (the reducer skips locked/hidden nodes).
|
||||||
if (props.selectionIds.includes(node.id)) {
|
if (props.selectionIds.includes(node.id)) {
|
||||||
if (node.locked) {
|
|
||||||
// Locked nodes are selectable, but should not start a move drag.
|
|
||||||
// Keep the current selection intact for multi-select parity.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
props.onBeginMove(e);
|
props.onBeginMove(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,73 @@
|
|||||||
import { ASTRALVIEW_SCHEMA_VERSION, type Screen } from './schema';
|
import { ASTRALVIEW_SCHEMA_VERSION, type Screen } from './schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for future schema migrations.
|
||||||
|
* Keep it pure and versioned.
|
||||||
|
*/
|
||||||
|
function toNumber(v: unknown, fallback: number): number {
|
||||||
|
return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function asString(v: unknown): string {
|
||||||
|
return typeof v === 'string' ? v : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeScreenV1(input: Screen): Screen {
|
||||||
|
const allowedTypes = new Set(['text', 'image', 'iframe', 'video']);
|
||||||
|
|
||||||
|
const nodes = Array.isArray(input.nodes) ? input.nodes : [];
|
||||||
|
|
||||||
|
const sanitizedNodes = nodes
|
||||||
|
.filter((n): n is Screen['nodes'][number] => !!n && typeof n === 'object')
|
||||||
|
.filter((n) => allowedTypes.has((n as { type?: unknown }).type as string))
|
||||||
|
.map((n, i) => {
|
||||||
|
const anyNode = n as unknown as Record<string, unknown>;
|
||||||
|
const rect = (anyNode.rect ?? {}) as Record<string, unknown>;
|
||||||
|
|
||||||
|
const base = {
|
||||||
|
...n,
|
||||||
|
id: asString(anyNode.id) || `import_${Date.now()}_${i}`,
|
||||||
|
rect: {
|
||||||
|
x: toNumber(rect.x, 0),
|
||||||
|
y: toNumber(rect.y, 0),
|
||||||
|
w: toNumber(rect.w, 100),
|
||||||
|
h: toNumber(rect.h, 60),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure required props exist for media widgets to prevent runtime crashes.
|
||||||
|
const props = (base as unknown as { props?: unknown }).props;
|
||||||
|
const propsObj = (props && typeof props === 'object' ? (props as Record<string, unknown>) : {}) as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
|
||||||
|
if (base.type === 'image') {
|
||||||
|
return { ...base, props: { ...propsObj, src: asString(propsObj.src) } };
|
||||||
|
}
|
||||||
|
if (base.type === 'iframe') {
|
||||||
|
return { ...base, props: { ...propsObj, src: asString(propsObj.src) } };
|
||||||
|
}
|
||||||
|
if (base.type === 'video') {
|
||||||
|
return { ...base, props: { ...propsObj, src: asString(propsObj.src) } };
|
||||||
|
}
|
||||||
|
if (base.type === 'text') {
|
||||||
|
return { ...base, props: { ...propsObj, text: asString(propsObj.text) || '' } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
id: input.id || `av_${Date.now()}`,
|
||||||
|
name: input.name || 'Untitled Screen',
|
||||||
|
width: toNumber(input.width, 1920),
|
||||||
|
height: toNumber(input.height, 1080),
|
||||||
|
nodes: sanitizedNodes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Placeholder for future schema migrations.
|
* Placeholder for future schema migrations.
|
||||||
* Keep it pure and versioned.
|
* Keep it pure and versioned.
|
||||||
@ -11,7 +79,9 @@ export function migrateScreen(input: unknown): Screen {
|
|||||||
}
|
}
|
||||||
const version = (s as Screen).version;
|
const version = (s as Screen).version;
|
||||||
if (version === ASTRALVIEW_SCHEMA_VERSION) {
|
if (version === ASTRALVIEW_SCHEMA_VERSION) {
|
||||||
return s as Screen;
|
// Even for the current version, we defensively sanitize to avoid runtime
|
||||||
|
// "assertNever" crashes when users import malformed JSON.
|
||||||
|
return sanitizeScreenV1(s as Screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future: apply incremental migrations.
|
// Future: apply incremental migrations.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user