Example #1
0
const selectAndCenterArtboard = (state: ApplicationState, artboard: Artboard) => {

  let workspace = getSelectedWorkspace(state);
  if (!workspace.stage.container) return state;

  const { width, height } = workspace.stage.container.getBoundingClientRect();

  state = centerStage(state, state.selectedWorkspaceId, artboard.bounds, true, workspace.stage.fullScreen ? workspace.stage.fullScreen.originalTranslate.zoom : true);

  // update translate
  workspace = getSelectedWorkspace(state);

  if (workspace.stage.fullScreen) {
    state = updateWorkspaceStage(state, workspace.$id, {
      smooth: true,
      fullScreen: {
        artboardId: artboard.$id,
        originalTranslate: workspace.stage.translate,
        originalArtboardBounds: artboard.bounds
      },
      translate: {
        zoom: 1,
        left: -artboard.bounds.left,
        top: -artboard.bounds.top
      }
    });
  }

  state = setWorkspaceSelection(state, workspace.$id, getStructReference(artboard));
  return state;
}
Example #2
0
const componentsPaneReducer = (state: ApplicationState, event: BaseEvent) => {
  switch(event.type) {
    case COMPONENTS_PANE_COMPONENT_CLICKED: {
      const { componentId } = event as ComponentsPaneComponentClicked;
      return setWorkspaceSelection(state, state.selectedWorkspaceId, getStructReference({$id: componentId, $type: AVAILABLE_COMPONENT}));
    }
  }
  return state;
}
Example #3
0
const handleArtboardSelectionFromAction = <T extends { sourceEvent: React.MouseEvent<any> }>(state: ApplicationState, ref: StructReference, event: T) => {
  const { sourceEvent } = event;
  const workspace = getSelectedWorkspace(state);
  return setWorkspaceSelection(state, workspace.$id, ref);
}
Example #4
0
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;
}
Example #5
0
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;
}