const artboardPaneReducer = (state: ApplicationState, event: BaseEvent) => { switch (event.type) { case ARTBOARD_PANE_ROW_CLICKED: { const { artboardId } = event as ArtboardPaneRowClicked; return selectAndCenterArtboard(state, getArtboardById(artboardId, state)); } } return state; };
const cssInspectorReducer = (state: ApplicationState, event: BaseEvent) => { switch(event.type) { case CSS_TOGGLE_DECLARATION_EYE_CLICKED: { const { artboardId, itemId, declarationName } = event as CSSToggleDeclarationEyeClicked; const workspace = getArtboardWorkspace(artboardId, state) const artboard = getArtboardById(artboardId, state); const disabledStyleDeclarations = workspace.disabledStyleDeclarations || {}; const scopeInfo = getStyleOwnerScopeInfo(itemId, artboard.document); const scopeKey = scopeInfo.join(""); const disabledItemDecls = disabledStyleDeclarations[scopeKey] || {}; return updateWorkspace(state, workspace.$id, { disabledStyleDeclarations: { ...disabledStyleDeclarations, [scopeKey]: { ...(disabledItemDecls as any), [declarationName]: disabledItemDecls[declarationName] ? null : scopeInfo } } }) } } return state; }
const artboardReducer = (state: ApplicationState, event: BaseEvent) => { switch(event.type) { case ARTBOARD_LOADED: { const { artboardId, dependencyUris, document, checksum, mount } = event as ArtboardLoaded; return updateArtboard(state, artboardId, { dependencyUris, document, loading: false, originalDocument: document, mount, checksum }); } case ARTBOARD_LOADING: { const { artboardId } = event as ArtboardLoaded; state = updateArtboard(state, artboardId, { loading: true }); state = deselectNotFoundItems(state); return state; } case FILE_CONTENT_CHANGED: { const { filePath } = event as FileChanged; const workspace = getSelectedWorkspace(state); for (const artboard of workspace.artboards) { if (artboard.dependencyUris && artboard.dependencyUris.indexOf(filePath) !== -1) { state = updateArtboard(state, artboard.$id, { loading: true }); } } return state; } case ARTBOARD_PATCHED: { const { artboardId, document, checksum, nativeObjectMap } = event as ArtboardPatched; const artboard = getArtboardById(artboardId, state); state = updateArtboard(state, artboardId, { document, loading: false, nativeObjectMap, originalDocument: checksum ? document : artboard.originalDocument, checksum: checksum || artboard.checksum }); state = deselectNotFoundItems(state); return state; } case ARTBOARD_DOM_PATCHED: { const { artboardId, nativeObjectMap } = event as ArtboardDOMPatched; state = updateArtboard(state, artboardId, { loading: false, nativeObjectMap }); state = deselectNotFoundItems(state); return state; } case ARTBOARD_RENDERED: { const { artboardId, nativeObjectMap } = event as ArtboardRendered; return updateArtboard(state, artboardId, { nativeObjectMap, loading: false }); } case STAGE_RESIZED: { const { width, height } = event as StageResized; return resizeFullScreenArtboard(state, width, height); } case REMOVED: { const { itemId, itemType } = event as Removed; if (itemType === ARTBOARD) { state = removeArtboard(itemId, state); } return state; } case ARTBOARD_DOM_INFO_COMPUTED: { const { artboardId, computedInfo } = event as ArtboardDOMInfoComputed; return updateArtboard(state, artboardId, { computedDOMInfo: computedInfo }); } case ARTBOARD_CREATED: { let { artboard } = event as ArtboardCreated; if (!artboard.bounds || artboard.bounds.top === 0 && artboard.bounds.left === 0) { artboard = moveArtboardToBestPosition(artboard, state); } const workspace = getSelectedWorkspace(state); state = updateWorkspace(state, workspace.$id, { artboards: [...workspace.artboards, artboard] }); state = roundArtboardBounds(artboard.$id, state); state = setWorkspaceSelection(state, workspace.$id, getStructReference(artboard)); return state; } } return state; }
const stageReducer = (state: ApplicationState, event: BaseEvent) => { switch(event.type) { case VISUAL_EDITOR_WHEEL: { const { workspaceId, metaKey, ctrlKey, deltaX, deltaY, canvasHeight, canvasWidth } = event as StageWheel; const workspace = getWorkspaceById(state, workspaceId); if (workspace.stage.fullScreen) { return state; } let translate = getStageTranslate(workspace.stage); if (metaKey || ctrlKey) { translate = centerTransformZoom(translate, boundsFromRect({ width: canvasWidth, height: canvasHeight }), clamp(translate.zoom + translate.zoom * deltaY / ZOOM_SENSITIVITY, MIN_ZOOM, MAX_ZOOM), workspace.stage.mousePosition); } else { translate = { ...translate, left: translate.left - deltaX, top: translate.top - deltaY }; } return updateWorkspaceStage(state, workspace.$id, { smooth: false, translate }); } case TOGGLE_TOOLS_SHORTCUT_PRESSED: { const workspace = getSelectedWorkspace(state); return updateWorkspaceStage(state, workspace.$id, { showTools: workspace.stage.showTools == null ? false : !workspace.stage.showTools }) } case STAGE_TOOL_EDIT_TEXT_KEY_DOWN: { const { sourceEvent, nodeId } = event as StageToolEditTextKeyDown; if (sourceEvent.key === "Escape") { // const workspace = getSyntheticNodeWorkspace(state, nodeId); // state = setWorkspaceSelection(state, workspace.$id, getStructReference(getNestedObjectById(nodeId, getNodeArtboard(nodeId, state).document))); // state = updateWorkspaceStage(state, workspace.$id, { // secondarySelection: false // }); } return state; } case RESIZER_MOVED: { const { point, workspaceId, point: newPoint } = event as ResizerMoved; const workspace = getSelectedWorkspace(state); state = updateWorkspaceStage(state, workspace.$id, { movingOrResizing: true }); const translate = getStageTranslate(workspace.stage); const selectionBounds = getWorkspaceSelectionBounds(workspace); for (const item of getBoundedWorkspaceSelection(workspace)) { const itemBounds = getWorkspaceItemBounds(item, workspace); // skip moving window if in full screen mode if (workspace.stage.fullScreen && workspace.stage.fullScreen.artboardId === item.$id) { break; } const newBounds = roundBounds(scaleInnerBounds(itemBounds, selectionBounds, moveBounds(selectionBounds, newPoint))); if (item.$type === ARTBOARD) { state = updateArtboard(state, item.$id, { bounds: newBounds }); } } return state; } case RESIZER_PATH_MOUSE_MOVED: { let { workspaceId, anchor, originalBounds, newBounds, sourceEvent } = event as ResizerPathMoved; const workspace = getSelectedWorkspace(state); state = updateWorkspaceStage(state, workspace.$id, { movingOrResizing: true }); // TODO - possibly use BoundsStruct instead of Bounds since there are cases where bounds prop doesn't exist const currentBounds = getWorkspaceSelectionBounds(workspace); const keepAspectRatio = sourceEvent.shiftKey; const keepCenter = sourceEvent.altKey; if (keepCenter) { // newBounds = keepBoundsCenter(newBounds, bounds, anchor); } if (keepAspectRatio) { newBounds = keepBoundsAspectRatio(newBounds, originalBounds, anchor, keepCenter ? { left: 0.5, top: 0.5 } : anchor); } for (const item of getBoundedWorkspaceSelection(workspace)) { const innerBounds = getWorkspaceItemBounds(item, workspace); const scaledBounds = scaleInnerBounds(currentBounds, currentBounds, newBounds); if (item.$type === ARTBOARD) { state = updateArtboard(state, item.$id, { bounds: scaleInnerBounds(innerBounds, currentBounds, newBounds) }) } } return state; } case RESIZER_PATH_MOUSE_STOPPED_MOVING: case RESIZER_STOPPED_MOVING: { const workspace = getSelectedWorkspace(state); state = updateWorkspaceStage(state, workspace.$id, { movingOrResizing: false }); return state; } case ARTBOARD_FOCUSED: { const { artboardId } = event as ArtboardFocused; return selectAndCenterArtboard(state, getArtboardById(artboardId, state)); } case STAGE_TOOL_OVERLAY_MOUSE_LEAVE: { const { sourceEvent } = event as StageToolOverlayMouseMoved; return updateWorkspace(state, state.selectedWorkspaceId, { hoveringRefs: [] }); } case CSS_DECLARATION_TITLE_MOUSE_ENTER: { const { artboardId, ruleId } = event as CSSDeclarationTitleMouseLeaveEnter; const artboard = getArtboardById(artboardId, state); // TODO return state; // const { selectorText }: SEnvCSSStyleRuleInterface = getNestedObjectById(ruleId, artboard.document); // return updateWorkspace(state, state.selectedWorkspaceId, { // hoveringRefs: getMatchingElements(artboard, selectorText).map((element) => [ // element.$type, // element.$id // ]) as [[string, string]] // }); } case CSS_DECLARATION_TITLE_MOUSE_LEAVE: { const { artboardId, ruleId } = event as CSSDeclarationTitleMouseLeaveEnter; return updateWorkspace(state, state.selectedWorkspaceId, { hoveringRefs: [] }); } case BREADCRUMB_ITEM_CLICKED: { const { artboardId, nodeId } = event as BreadcrumbItemClicked; const artboard = getArtboardById(artboardId, state); const node = getNestedObjectById(nodeId, artboard.document) as SlimBaseNode; const workspace = getArtboardWorkspace(artboard.$id, state); return setWorkspaceSelection(state, workspace.$id, [node.type, node.id]); } case BREADCRUMB_ITEM_MOUSE_ENTER: { const { artboardId, nodeId } = event as BreadcrumbItemMouseEnterLeave; return updateWorkspace(state, state.selectedWorkspaceId, { hoveringRefs: [[SlimVMObjectType.ELEMENT, nodeId]] }); } case BREADCRUMB_ITEM_MOUSE_LEAVE: { const { artboardId, nodeId } = event as BreadcrumbItemMouseEnterLeave; return updateWorkspace(state, state.selectedWorkspaceId, { hoveringRefs: [] }); } case STAGE_MOUNTED: { const { element } = event as StageMounted; const { width = 400, height = 300 } = element.getBoundingClientRect() || {}; const workspaceId = state.selectedWorkspaceId; const workspace = getSelectedWorkspace(state); state = updateWorkspaceStage(state, workspaceId, { container: element }); // do not center if in full screen mode if (workspace.stage.fullScreen) { return updateArtboardSize(state, workspace.stage.fullScreen.artboardId, width, height); } return centerSelectedWorkspace(state); }; case STAGE_TOOL_OVERLAY_MOUSE_PAN_START: { const { artboardId } = event as StageToolOverlayMousePanStart; const workspace = getArtboardWorkspace(artboardId, state); return updateWorkspaceStage(state, workspace.$id, { panning: true }); } case STAGE_TOOL_OVERLAY_MOUSE_PAN_END: { const { artboardId } = event as StageToolOverlayMousePanEnd; const workspace = getArtboardWorkspace(artboardId, state) return updateWorkspaceStage(state, workspace.$id, { panning: false }); } case STAGE_MOUSE_MOVED: { const { sourceEvent: { pageX, pageY }} = event as WrappedEvent<React.MouseEvent<any>>; state = updateWorkspaceStage(state, state.selectedWorkspaceId, { mousePosition: { left: pageX, top: pageY } }); const workspace = getSelectedWorkspace(state); // TODO - in the future, we'll probably want to be able to highlight hovered nodes as the user is moving an element around to indicate where // they can drop the element. const targetRef = workspace.stage.movingOrResizing ? null : getStageToolMouseNodeTargetReference(state, event as StageToolOverlayMouseMoved); state = updateWorkspace(state, state.selectedWorkspaceId, { hoveringRefs: targetRef ? [targetRef] : [] }); return state; }; case STAGE_MOUSE_CLICKED: { const { sourceEvent } = event as StageToolNodeOverlayClicked; if (/textarea|input/i.test((sourceEvent.target as Element).nodeName)) { return state; } // alt key opens up a new link const altKey = sourceEvent.altKey; const workspace = getSelectedWorkspace(state); state = updateWorkspaceStageSmoothing(state, workspace); // do not allow selection while window is panning (scrolling) if (workspace.stage.panning || workspace.stage.movingOrResizing) return state; const targetRef = getStageToolMouseNodeTargetReference(state, event as StageToolNodeOverlayClicked); if (!targetRef) { return state; } if (!altKey) { state = handleArtboardSelectionFromAction(state, targetRef, event as StageToolNodeOverlayClicked); state = updateWorkspaceStage(state, workspace.$id, { secondarySelection: false }); return state; } return state; } case STAGE_TOOL_OVERLAY_MOUSE_DOUBLE_CLICKED: { const { sourceEvent, artboardId } = event as StageToolNodeOverlayClicked; const workspace = getArtboardWorkspace(artboardId, state); const targetRef = getStageToolMouseNodeTargetReference(state, event as StageToolNodeOverlayClicked); if (!targetRef) return state; state = updateWorkspaceStage(state, workspace.$id, { secondarySelection: true }); state = setWorkspaceSelection(state, workspace.$id, targetRef); return state; } case ARTBOARD_SELECTION_SHIFTED: { const { artboardId } = event as ArtboardSelectionShifted; return selectAndCenterArtboard(state, getArtboardById(artboardId, state)); } case SELECTOR_DOUBLE_CLICKED: { const { sourceEvent, item } = event as SelectorDoubleClicked; const workspace = getSyntheticNodeWorkspace(state, item.$id); state = updateWorkspaceStage(state, workspace.$id, { secondarySelection: true }); state = setWorkspaceSelection(state, workspace.$id, getStructReference(item)); return state; } case ARTBOARD_SCROLL: { const { artboardId, scrollPosition } = event as ArtboardScroll; return updateArtboard(state, artboardId, { scrollPosition }); } case WORKSPACE_DELETION_SELECTED: { const { workspaceId } = event as WorkspaceSelectionDeleted; state = clearWorkspaceSelection(state, workspaceId); return state; } case STAGE_TOOL_ARTBOARD_TITLE_CLICKED: { state = updateWorkspaceStageSmoothing(state); return handleArtboardSelectionFromAction(state, getStructReference(getArtboardById((event as ArtboardPaneRowClicked).artboardId, state)), event as ArtboardPaneRowClicked); } case STAGE_TOOL_WINDOW_BACKGROUND_CLICKED: { const workspace = getSelectedWorkspace(state); return clearWorkspaceSelection(state, workspace.$id); } } return state; }
const shortcutReducer = (state: ApplicationState, event: BaseEvent) => { switch(event.type) { case TOGGLE_LEFT_GUTTER_PRESSED: { const workspace = getSelectedWorkspace(state); return updateWorkspaceStage(state, workspace.$id, { showLeftGutter: !workspace.stage.showLeftGutter }); } case ESCAPE_SHORTCUT_PRESSED: { const workspace = getSelectedWorkspace(state); return updateWorkspace(state, workspace.$id, { selectionRefs: [] }); } case ZOOM_IN_SHORTCUT_PRESSED: { const workspace = getSelectedWorkspace(state); if (workspace.stage.fullScreen) return state; return setStageZoom(state, workspace.$id, normalizeZoom(workspace.stage.translate.zoom) * 2); } case ZOOM_OUT_SHORTCUT_PRESSED: { const workspace = getSelectedWorkspace(state); if (workspace.stage.fullScreen) return state; return setStageZoom(state, workspace.$id, normalizeZoom(workspace.stage.translate.zoom) / 2); } case PREV_ARTBOARD_SHORTCUT_PRESSED: { return state; } case FULL_SCREEN_TARGET_DELETED: { return unfullscreen(state); } case FULL_SCREEN_SHORTCUT_PRESSED: { const workspace = getSelectedWorkspace(state); const selection = workspace.selectionRefs[0]; const artboardId = selection ? selection[0] === ARTBOARD ? selection[1] : getNodeArtboard(selection[1], state) && getNodeArtboard(selection[1], state).$id : null; if (artboardId && !workspace.stage.fullScreen) { const artboard = getArtboardById(artboardId, state); state = updateWorkspaceStage(state, workspace.$id, { smooth: true, fullScreen: { artboardId: artboardId, originalTranslate: workspace.stage.translate, originalArtboardBounds: artboard.bounds }, translate: { zoom: 1, left: -artboard.bounds.left, top: -artboard.bounds.top } }); const updatedWorkspace = getSelectedWorkspace(state); state = updateArtboard(state, artboardId, { bounds: { left: artboard.bounds.left, top: artboard.bounds.top, right: artboard.bounds.left + workspace.stage.container.getBoundingClientRect().width, bottom: artboard.bounds.top + workspace.stage.container.getBoundingClientRect().height } }); return state; } else if (workspace.stage.fullScreen) { return unfullscreen(state); } else { return state; } } case CANVAS_MOTION_RESTED: { const workspace = getSelectedWorkspace(state); return updateWorkspaceStage(state, workspace.$id, { smooth: false }); } case TOGGLE_TEXT_EDITOR_PRESSED: { const workspace = getSelectedWorkspace(state); return updateWorkspaceStage(state, workspace.$id, { showTextEditor: !workspace.stage.showTextEditor }); } case TOGGLE_RIGHT_GUTTER_PRESSED: { const workspace = getSelectedWorkspace(state); return updateWorkspaceStage(state, workspace.$id, { showRightGutter: !workspace.stage.showRightGutter }); } } return state; }