Ejemplo n.º 1
0
function* handleSelectionResized() {
  while(true) {
    let { workspaceId, anchor, originalBounds, newBounds, sourceEvent } = (yield take(RESIZER_PATH_MOUSE_MOVED)) as ResizerPathMoved;

    const state: ApplicationState = yield select();
 
    const workspace = getWorkspaceById(state, workspaceId);

    // 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);
      yield put(resized(item.$id, item.$type, scaleInnerBounds(innerBounds, currentBounds, newBounds), workspace.targetCSSSelectors));
    }
  }
}
Ejemplo n.º 2
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;
}