refactor: improve goView import (video/iframe) + editor state
This commit is contained in:
parent
858097bfba
commit
01b7a3a722
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
assertNever,
|
||||||
convertGoViewProjectToScreen,
|
convertGoViewProjectToScreen,
|
||||||
createEmptyScreen,
|
createEmptyScreen,
|
||||||
migrateScreen,
|
migrateScreen,
|
||||||
@ -78,13 +79,13 @@ interface BoxSelectSession {
|
|||||||
baseIds: string[];
|
baseIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type InternalEditorState = EditorState & {
|
type EditorRuntimeState = EditorState & {
|
||||||
__pan?: PanSession;
|
__pan?: PanSession;
|
||||||
__boxSelect?: BoxSelectSession;
|
__boxSelect?: BoxSelectSession;
|
||||||
__drag?: DragSession;
|
__drag?: DragSession;
|
||||||
};
|
};
|
||||||
|
|
||||||
function historyPush(state: EditorState): EditorState {
|
function historyPush(state: EditorRuntimeState): EditorRuntimeState {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
history: {
|
history: {
|
||||||
@ -94,12 +95,12 @@ function historyPush(state: EditorState): EditorState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureSelected(state: EditorState, id: string): EditorState {
|
function ensureSelected(state: EditorRuntimeState, id: string): EditorRuntimeState {
|
||||||
if (state.selection.ids.includes(id)) return state;
|
if (state.selection.ids.includes(id)) return state;
|
||||||
return { ...state, selection: { ids: [id] } };
|
return { ...state, selection: { ids: [id] } };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInitialState(): EditorState {
|
export function createInitialState(): EditorRuntimeState {
|
||||||
const screen = createEmptyScreen({
|
const screen = createEmptyScreen({
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
@ -141,8 +142,8 @@ export function createInitialState(): EditorState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function editorReducer(state: EditorState, action: EditorAction): EditorState {
|
export function editorReducer(state: EditorRuntimeState, action: EditorAction): EditorRuntimeState {
|
||||||
const s = state as InternalEditorState;
|
const s = state;
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'keyboard':
|
case 'keyboard':
|
||||||
return { ...state, keyboard: { ctrl: action.ctrl, space: action.space } };
|
return { ...state, keyboard: { ctrl: action.ctrl, space: action.space } };
|
||||||
@ -454,7 +455,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
startPanX: state.canvas.panX,
|
startPanX: state.canvas.panX,
|
||||||
startPanY: state.canvas.panY,
|
startPanY: state.canvas.panY,
|
||||||
},
|
},
|
||||||
} as EditorState;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updatePan': {
|
case 'updatePan': {
|
||||||
@ -476,15 +477,14 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
|
|
||||||
case 'endPan': {
|
case 'endPan': {
|
||||||
if (!state.canvas.isPanning) return state;
|
if (!state.canvas.isPanning) return state;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { __pan: _pan, ...rest } = s;
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...state,
|
||||||
canvas: {
|
canvas: {
|
||||||
...state.canvas,
|
...state.canvas,
|
||||||
isPanning: false,
|
isPanning: false,
|
||||||
},
|
},
|
||||||
} as EditorState;
|
__pan: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'selectSingle':
|
case 'selectSingle':
|
||||||
@ -525,7 +525,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
},
|
},
|
||||||
selection: { ids: baseIds },
|
selection: { ids: baseIds },
|
||||||
__boxSelect: { additive: action.additive, baseIds },
|
__boxSelect: { additive: action.additive, baseIds },
|
||||||
} as EditorState;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateBoxSelect': {
|
case 'updateBoxSelect': {
|
||||||
@ -567,16 +567,15 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
|
|
||||||
case 'endBoxSelect': {
|
case 'endBoxSelect': {
|
||||||
if (!state.canvas.isBoxSelecting) return state;
|
if (!state.canvas.isBoxSelecting) return state;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { __boxSelect: _box, ...rest } = s;
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...state,
|
||||||
canvas: {
|
canvas: {
|
||||||
...state.canvas,
|
...state.canvas,
|
||||||
isBoxSelecting: false,
|
isBoxSelecting: false,
|
||||||
mouse: { ...state.canvas.mouse, offsetX: 0, offsetY: 0, offsetStartX: 0, offsetStartY: 0 },
|
mouse: { ...state.canvas.mouse, offsetX: 0, offsetY: 0, offsetStartX: 0, offsetStartY: 0 },
|
||||||
},
|
},
|
||||||
} as EditorState;
|
__boxSelect: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'beginMove': {
|
case 'beginMove': {
|
||||||
@ -614,7 +613,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
__drag: drag,
|
__drag: drag,
|
||||||
} as EditorState;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateMove': {
|
case 'updateMove': {
|
||||||
@ -669,17 +668,19 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
|
|
||||||
case 'endMove': {
|
case 'endMove': {
|
||||||
const drag = s.__drag;
|
const drag = s.__drag;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { __drag: _drag, ...rest } = s;
|
|
||||||
|
|
||||||
if (!drag || drag.kind !== 'move') {
|
if (!drag || drag.kind !== 'move') {
|
||||||
return { ...rest, canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } } };
|
return {
|
||||||
|
...state,
|
||||||
|
canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } },
|
||||||
|
__drag: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const changed = didRectsChange(drag.snapshot, state.doc.screen);
|
const changed = didRectsChange(drag.snapshot, state.doc.screen);
|
||||||
return {
|
return {
|
||||||
...rest,
|
...state,
|
||||||
canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } },
|
canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } },
|
||||||
|
__drag: undefined,
|
||||||
history: changed
|
history: changed
|
||||||
? {
|
? {
|
||||||
past: [...state.history.past, { screen: drag.beforeScreen }],
|
past: [...state.history.past, { screen: drag.beforeScreen }],
|
||||||
@ -709,7 +710,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
...next,
|
...next,
|
||||||
canvas: { ...next.canvas, isDragging: true, guides: { xs: [], ys: [] } },
|
canvas: { ...next.canvas, isDragging: true, guides: { xs: [], ys: [] } },
|
||||||
__drag: drag,
|
__drag: drag,
|
||||||
} as EditorState;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateResize': {
|
case 'updateResize': {
|
||||||
@ -774,17 +775,19 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
|
|
||||||
case 'endResize': {
|
case 'endResize': {
|
||||||
const drag = s.__drag;
|
const drag = s.__drag;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { __drag: _drag, ...rest } = s;
|
|
||||||
|
|
||||||
if (!drag || drag.kind !== 'resize') {
|
if (!drag || drag.kind !== 'resize') {
|
||||||
return { ...rest, canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } } };
|
return {
|
||||||
|
...state,
|
||||||
|
canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } },
|
||||||
|
__drag: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const changed = didRectsChange(drag.snapshot, state.doc.screen);
|
const changed = didRectsChange(drag.snapshot, state.doc.screen);
|
||||||
return {
|
return {
|
||||||
...rest,
|
...state,
|
||||||
canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } },
|
canvas: { ...state.canvas, isDragging: false, guides: { xs: [], ys: [] } },
|
||||||
|
__drag: undefined,
|
||||||
history: changed
|
history: changed
|
||||||
? {
|
? {
|
||||||
past: [...state.history.past, { screen: drag.beforeScreen }],
|
past: [...state.history.past, { screen: drag.beforeScreen }],
|
||||||
@ -830,7 +833,7 @@ export function editorReducer(state: EditorState, action: EditorAction): EditorS
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return assertNever(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -270,7 +270,22 @@ function looksLikeIframeOption(option: unknown): boolean {
|
|||||||
if (
|
if (
|
||||||
hasAnyKeyDeep(option, ['iframeUrl', 'iframeSrc', 'embedUrl', 'frameUrl', 'frameSrc'], 2) ||
|
hasAnyKeyDeep(option, ['iframeUrl', 'iframeSrc', 'embedUrl', 'frameUrl', 'frameSrc'], 2) ||
|
||||||
// Some exports store raw HTML instead of a URL.
|
// Some exports store raw HTML instead of a URL.
|
||||||
hasAnyKeyDeep(option, ['srcdoc', 'srcDoc', 'html', 'htmlContent', 'content', 'template'], 2)
|
hasAnyKeyDeep(
|
||||||
|
option,
|
||||||
|
[
|
||||||
|
'srcdoc',
|
||||||
|
'srcDoc',
|
||||||
|
'html',
|
||||||
|
'htmlContent',
|
||||||
|
'htmlString',
|
||||||
|
'iframeHtml',
|
||||||
|
'embedHtml',
|
||||||
|
'embedCode',
|
||||||
|
'content',
|
||||||
|
'template',
|
||||||
|
],
|
||||||
|
2,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -401,6 +416,12 @@ function normalizeOption(option: unknown): unknown {
|
|||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toMaybeString(v: unknown): string | undefined {
|
||||||
|
if (typeof v === 'string') return v;
|
||||||
|
if (typeof v === 'number' && Number.isFinite(v)) return String(v);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function toNumber(v: unknown, fallback: number): number {
|
function toNumber(v: unknown, fallback: number): number {
|
||||||
if (typeof v === 'number' && Number.isFinite(v)) return v;
|
if (typeof v === 'number' && Number.isFinite(v)) return v;
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') {
|
||||||
@ -451,6 +472,15 @@ function pickSizeLike(option: unknown): { w?: number; h?: number } {
|
|||||||
return { w, h };
|
return { w, h };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeRect(
|
||||||
|
rect: { x: number; y: number; w: number; h: number },
|
||||||
|
fallback: { w: number; h: number },
|
||||||
|
): { x: number; y: number; w: number; h: number } {
|
||||||
|
const w = rect.w > 0 ? rect.w : fallback.w;
|
||||||
|
const h = rect.h > 0 ? rect.h : fallback.h;
|
||||||
|
return { x: rect.x, y: rect.y, w, h };
|
||||||
|
}
|
||||||
|
|
||||||
export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewStorageLike): Screen {
|
export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewStorageLike): Screen {
|
||||||
// goView exports vary a lot; attempt a few common nesting shapes.
|
// goView exports vary a lot; attempt a few common nesting shapes.
|
||||||
const root = input as unknown as Record<string, unknown>;
|
const root = input as unknown as Record<string, unknown>;
|
||||||
@ -566,7 +596,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
const optSize = pickSizeLike(option);
|
const optSize = pickSizeLike(option);
|
||||||
|
|
||||||
const attr = c.attr as unknown as Record<string, unknown> | undefined;
|
const attr = c.attr as unknown as Record<string, unknown> | undefined;
|
||||||
const rect = attr
|
const rawRect = attr
|
||||||
? {
|
? {
|
||||||
x: toNumber(attr.x, 0),
|
x: toNumber(attr.x, 0),
|
||||||
y: toNumber(attr.y, 0),
|
y: toNumber(attr.y, 0),
|
||||||
@ -575,13 +605,15 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
h: toNumber(attr.h, optSize.h ?? defaults.h),
|
h: toNumber(attr.h, optSize.h ?? defaults.h),
|
||||||
}
|
}
|
||||||
: { x: 0, y: 0, w: optSize.w ?? defaults.w, h: optSize.h ?? defaults.h };
|
: { x: 0, y: 0, w: optSize.w ?? defaults.w, h: optSize.h ?? defaults.h };
|
||||||
|
const rect = normalizeRect(rawRect, defaults);
|
||||||
|
|
||||||
const zIndex = attr?.zIndex === undefined ? undefined : toNumber(attr.zIndex, 0);
|
const zIndex = attr?.zIndex === undefined ? undefined : toNumber(attr.zIndex, 0);
|
||||||
|
const baseId = toMaybeString(c.id);
|
||||||
|
|
||||||
if (inferredType === 'text') {
|
if (inferredType === 'text') {
|
||||||
const props = convertGoViewTextOptionToNodeProps(option as GoViewTextOption);
|
const props = convertGoViewTextOptionToNodeProps(option as GoViewTextOption);
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: c.id ?? `import_text_${Math.random().toString(16).slice(2)}`,
|
id: baseId ?? `import_text_${Math.random().toString(16).slice(2)}`,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
rect,
|
rect,
|
||||||
zIndex,
|
zIndex,
|
||||||
@ -595,7 +627,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
if (inferredType === 'image') {
|
if (inferredType === 'image') {
|
||||||
const props = convertGoViewImageOptionToNodeProps(option as GoViewImageOption);
|
const props = convertGoViewImageOptionToNodeProps(option as GoViewImageOption);
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: c.id ?? `import_image_${Math.random().toString(16).slice(2)}`,
|
id: baseId ?? `import_image_${Math.random().toString(16).slice(2)}`,
|
||||||
type: 'image',
|
type: 'image',
|
||||||
rect,
|
rect,
|
||||||
zIndex,
|
zIndex,
|
||||||
@ -609,7 +641,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
if (inferredType === 'iframe') {
|
if (inferredType === 'iframe') {
|
||||||
const props = convertGoViewIframeOptionToNodeProps(option as GoViewIframeOption);
|
const props = convertGoViewIframeOptionToNodeProps(option as GoViewIframeOption);
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: c.id ?? `import_iframe_${Math.random().toString(16).slice(2)}`,
|
id: baseId ?? `import_iframe_${Math.random().toString(16).slice(2)}`,
|
||||||
type: 'iframe',
|
type: 'iframe',
|
||||||
rect,
|
rect,
|
||||||
zIndex,
|
zIndex,
|
||||||
@ -623,7 +655,7 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
if (inferredType === 'video') {
|
if (inferredType === 'video') {
|
||||||
const props = convertGoViewVideoOptionToNodeProps(option as GoViewVideoOption);
|
const props = convertGoViewVideoOptionToNodeProps(option as GoViewVideoOption);
|
||||||
nodes.push({
|
nodes.push({
|
||||||
id: c.id ?? `import_video_${Math.random().toString(16).slice(2)}`,
|
id: baseId ?? `import_video_${Math.random().toString(16).slice(2)}`,
|
||||||
type: 'video',
|
type: 'video',
|
||||||
rect,
|
rect,
|
||||||
zIndex,
|
zIndex,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user