From fb6733807f31179fa30a4073ae3f873a6cd64fea Mon Sep 17 00:00:00 2001 From: Connor Turland <1409121+Connoropolous@users.noreply.github.com> Date: Fri, 22 Sep 2023 14:31:34 -0700 Subject: [PATCH 1/3] big refactoring of connection connector and outcome form state --- .../MapViewOutcomeTitleForm.component.tsx | 16 +- .../MapViewOutcomeTitleForm.connector.ts | 21 +- .../OutcomeConnectors.component.tsx | 236 ++++++++++++------ .../OutcomeConnectors.connector.ts | 50 ++-- web/src/drawing/drawConnection.ts | 8 +- web/src/drawing/eventDetection.ts | 5 +- web/src/drawing/index.ts | 3 +- web/src/drawing/layoutFormula.ts | 2 - web/src/event-listeners/index.ts | 49 ++-- web/src/redux/ephemeral/layout/reducer.ts | 16 +- .../ephemeral/outcome-connector/actions.ts | 31 +-- .../ephemeral/outcome-connector/handler.ts | 52 ++-- .../ephemeral/outcome-connector/reducer.js | 43 ---- .../ephemeral/outcome-connector/reducer.ts | 44 ++++ .../redux/ephemeral/outcome-form/actions.js | 53 ---- .../redux/ephemeral/outcome-form/actions.ts | 46 ++++ .../redux/ephemeral/outcome-form/reducer.js | 66 ----- .../redux/ephemeral/outcome-form/reducer.ts | 65 +++++ .../types/createOutcomeWithConnectionInput.ts | 6 +- 19 files changed, 425 insertions(+), 387 deletions(-) delete mode 100644 web/src/redux/ephemeral/outcome-connector/reducer.js create mode 100644 web/src/redux/ephemeral/outcome-connector/reducer.ts delete mode 100644 web/src/redux/ephemeral/outcome-form/actions.js create mode 100644 web/src/redux/ephemeral/outcome-form/actions.ts delete mode 100644 web/src/redux/ephemeral/outcome-form/reducer.js create mode 100644 web/src/redux/ephemeral/outcome-form/reducer.ts diff --git a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx b/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx index 421bed23..c190643e 100644 --- a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx +++ b/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.component.tsx @@ -26,21 +26,15 @@ export type MapViewOutcomeTitleFormOwnProps = { export type MapViewOutcomeTitleFormConnectorStateProps = { activeAgentPubKey: AgentPubKeyB64 - scale + scale: number // the value of the text input content: string // coordinates in css terms for the box leftConnectionXPosition: number topConnectionYPosition: number - // (optional) the address of an Outcome to connect this Outcome to - // in the case of creating an Outcome - fromAddress: ActionHashB64 - // (optional) the relation (relation_as_{child|parent}) between the two - // in the case of creating an Outcome - relation: RelationInput + maybeLinkedOutcome: Option // (optional) the address of an existing connection that // indicates this Outcome as the child of another (a.k.a has a parent) - // ASSUMPTION: one parent existingParentConnectionAddress: ActionHashB64 } @@ -63,8 +57,7 @@ const MapViewOutcomeTitleForm: React.FC = ({ activeAgentPubKey, scale, content, - fromAddress, - relation, + maybeLinkedOutcome, existingParentConnectionAddress, leftConnectionXPosition, topConnectionYPosition, @@ -147,7 +140,8 @@ const MapViewOutcomeTitleForm: React.FC = ({ isImported: false, githubLink: '', }, - fromAddress ? { outcomeActionHash: fromAddress, relation, siblingOrder: 0 } : null + // could be null and that's ok + maybeLinkedOutcome ) } diff --git a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts b/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts index afaa5762..a4fcbb55 100644 --- a/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts +++ b/web/src/components/MapViewOutcomeTitleForm/MapViewOutcomeTitleForm.connector.ts @@ -8,7 +8,7 @@ import { closeOutcomeForm, updateContent, } from '../../redux/ephemeral/outcome-form/actions' -import MapViewOutcomeTitleForm, { MapViewOutcomeTitleFormConnectorDispatchProps, MapViewOutcomeTitleFormOwnProps } from './MapViewOutcomeTitleForm.component' +import MapViewOutcomeTitleForm, { MapViewOutcomeTitleFormConnectorDispatchProps, MapViewOutcomeTitleFormConnectorStateProps, MapViewOutcomeTitleFormOwnProps } from './MapViewOutcomeTitleForm.component' import ProjectsZomeApi from '../../api/projectsApi' import { getAppWs } from '../../hcWebsockets' import { cellIdFromString } from '../../utils' @@ -23,7 +23,7 @@ import { LAYOUT_ANIMATION_TYPICAL_MS } from '../../constants' // to pass it to a component for rendering, as specific properties that // that component expects -function mapStateToProps(state: RootState) { +function mapStateToProps(state: RootState): MapViewOutcomeTitleFormConnectorStateProps { const { ui: { viewport: { scale }, @@ -32,27 +32,16 @@ function mapStateToProps(state: RootState) { content, leftConnectionXPosition, topConnectionYPosition, - // these three all relate to each other - fromAddress, - relation, - // ASSUMPTION: one parent - existingParentConnectionAddress, // this is optional though + maybeLinkedOutcome, + existingParentConnectionAddress, }, }, } = state return { + maybeLinkedOutcome, activeAgentPubKey: state.agentAddress, scale, - // optional, the address of the Outcome that we are relating this to - fromAddress, - // optional, the relation (relation_as_{child|parent}) - // between the potential fromAddress Outcome - // and a new Outcome to be created - relation, - // optional, the address of an existing connection that - // indicates this Outcome as the child of another (a.k.a has a parent) - // ASSUMPTION: one parent existingParentConnectionAddress, content, leftConnectionXPosition: leftConnectionXPosition, diff --git a/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx b/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx index ab12c80c..4c24f9aa 100644 --- a/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx +++ b/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx @@ -1,13 +1,73 @@ import React from 'react' import './OutcomeConnectors.scss' import { CONNECTOR_VERTICAL_SPACING } from '../../drawing/dimensions' -import { - RELATION_AS_CHILD, - RELATION_AS_PARENT, -} from '../../redux/ephemeral/outcome-connector/actions' import { coordsCanvasToPage } from '../../drawing/coordinateSystems' import handleConnectionConnectMouseUp from '../../redux/ephemeral/outcome-connector/handler' import { calculateValidChildren, calculateValidParents } from '../../tree-logic' +import { + ComputedOutcome, + Connection, + LinkedOutcomeDetails, + RelationInput, +} from '../../types' +import { OutcomeConnectorFromPayload } from '../../redux/ephemeral/outcome-connector/actions' +import { + ActionHashB64, + CellIdString, + Option, + WithActionHash, +} from '../../types/shared' +import { ViewportState } from '../../redux/ephemeral/viewport/state-type' +import { + CoordinatesState, + DimensionsState, +} from '../../redux/ephemeral/layout/state-type' + +const { ExistingOutcomeAsChild, ExistingOutcomeAsParent } = RelationInput + +export type OutcomeConnectorCommonStateProps = { + activeProject: CellIdString + translate: ViewportState['translate'] + zoomLevel: ViewportState['scale'] + connections: WithActionHash[] + maybeLinkedOutcome: Option + toAddress: ActionHashB64 +} + +export type OutcomeConnectorCommonDispatchProps = { + setOutcomeConnectorFrom: (payload: OutcomeConnectorFromPayload) => void + setOutcomeConnectorTo: (address: ActionHashB64) => void + dispatch: any +} + +// Single +export type OutcomeConnectorProps = OutcomeConnectorCommonStateProps & + OutcomeConnectorCommonDispatchProps & { + ownExistingParentConnectionAddress: ActionHashB64 + presetExistingParentConnectionAddress: ActionHashB64 + address: ActionHashB64 + outcomeCoordinates: CoordinatesState[ActionHashB64] + outcomeDimensions: DimensionsState[ActionHashB64] + isCollapsed: boolean + outcomeActionHashes: ActionHashB64[] + } + +// Plural +export type OutcomeConnectorsStateProps = OutcomeConnectorCommonStateProps & { + collapsedOutcomes: { + [outcomeActionHash: string]: boolean + } + coordinates: CoordinatesState + dimensions: DimensionsState + existingParentConnectionAddress: ActionHashB64 + connectorAddresses: ActionHashB64[] +} +export type OutcomeConnectorsProps = OutcomeConnectorsStateProps & + OutcomeConnectorCommonDispatchProps & { + outcomes: { + [actionHash: ActionHashB64]: ComputedOutcome + } + } const OutcomeConnectorHtml = ({ active, @@ -34,23 +94,28 @@ const OutcomeConnectorHtml = ({ const OutcomeConnector = ({ activeProject, - ownExistingParentConnectionAddress, - presetExistingParentConnectionAddress, - fromAddress, - relation, + maybeLinkedOutcome, toAddress, - address, - outcomeCoordinates, - outcomeDimensions, - isCollapsed, setOutcomeConnectorFrom, setOutcomeConnectorTo, connections, - outcomeActionHashes, translate, zoomLevel, dispatch, -}) => { + // specific to this Outcome Connector + ownExistingParentConnectionAddress, + presetExistingParentConnectionAddress, + address, + outcomeCoordinates, + outcomeDimensions, + isCollapsed, + outcomeActionHashes, +}: OutcomeConnectorProps) => { + if (!maybeLinkedOutcome) { + return null + } + const { outcomeActionHash: fromAddress, relation } = maybeLinkedOutcome + // calculate the coordinates on the page, based // on what the coordinates on the canvas would be const { x: topConnectorLeft, y: topConnectorTop } = coordsCanvasToPage( @@ -74,27 +139,31 @@ const OutcomeConnector = ({ ) const topConnectorActive = - (address === fromAddress && relation === RELATION_AS_CHILD) || - (toAddress && address === toAddress && relation === RELATION_AS_PARENT) + (address === fromAddress && relation === ExistingOutcomeAsChild) || + (toAddress && address === toAddress && relation === ExistingOutcomeAsParent) const bottomConnectorActive = - (address === fromAddress && relation === RELATION_AS_PARENT) || - (toAddress && address === toAddress && relation === RELATION_AS_CHILD) + (address === fromAddress && relation === ExistingOutcomeAsParent) || + (toAddress && address === toAddress && relation === ExistingOutcomeAsChild) // a connection to this upper port would make this Outcome a child of the // current 'from' Outcome of the connection connector // if there is one const canShowTopConnector = - !bottomConnectorActive && (!relation || relation === RELATION_AS_PARENT) + !bottomConnectorActive && + (!relation || relation === ExistingOutcomeAsParent) // a connection to this lower port would make this Outcome a parent of the current 'from' Outcome of the connection connector // if there is one const canShowBottomConnector = !topConnectorActive && - (!relation || relation === RELATION_AS_CHILD) && + (!relation || relation === ExistingOutcomeAsChild) && !isCollapsed - // shared code for mouse event handlers - const connectionConnectMouseDown = (direction, validity) => (event: React.MouseEvent) => { + // generalized mouse down handler + const connectionConnectMouseDown = ( + direction: RelationInput, + validity: typeof calculateValidParents + ) => (event: React.MouseEvent) => { if (!fromAddress) { // if the action is being performed from // a top port, then there's two options: @@ -103,35 +172,42 @@ const OutcomeConnector = ({ // if the action is a bottom port, then // definitely don't override any existing connection // ASSUMPTION: one parent - const connectionAddressToOverride = (direction === RELATION_AS_CHILD && !event.shiftKey) ? ownExistingParentConnectionAddress : undefined - setOutcomeConnectorFrom( - address, - direction, - validity(address, connections, outcomeActionHashes), - connectionAddressToOverride - ) + const connectionAddressToOverride = + direction === ExistingOutcomeAsChild && !event.shiftKey + ? ownExistingParentConnectionAddress + : undefined + setOutcomeConnectorFrom({ + maybeLinkedOutcome: { + outcomeActionHash: address, + relation: direction, + siblingOrder: 0, // TODO? + }, + existingParentConnectionAddress: connectionAddressToOverride, + validToAddresses: validity(address, connections, outcomeActionHashes), + }) } } + + // mouse up handler const connectionConnectMouseUp = () => { handleConnectionConnectMouseUp( - fromAddress, - relation, + maybeLinkedOutcome, toAddress, - // ASSUMPTION: one parent presetExistingParentConnectionAddress, activeProject, dispatch ) } + // spefific mousedown handlers const topConnectorOnMouseDown = connectionConnectMouseDown( - RELATION_AS_CHILD, + ExistingOutcomeAsChild, calculateValidParents ) const bottomConnectorOnMouseDown = connectionConnectMouseDown( - RELATION_AS_PARENT, + ExistingOutcomeAsParent, calculateValidChildren ) - + // mouseovers const connectorOnMouseOver = () => { // cannot set 'to' the very same Outcome if (fromAddress && address !== fromAddress) setOutcomeConnectorTo(address) @@ -179,8 +255,7 @@ const OutcomeConnectors = ({ connections, coordinates, dimensions, - fromAddress, - relation, + maybeLinkedOutcome, toAddress, existingParentConnectionAddress, connectorAddresses, @@ -188,48 +263,53 @@ const OutcomeConnectors = ({ setOutcomeConnectorFrom, setOutcomeConnectorTo, dispatch, -}) => { +}: OutcomeConnectorsProps) => { // convert from object to array const outcomeActionHashes = Object.keys(outcomes) - return connectorAddresses.map((connectorAddress) => { - const outcomeCoordinates = coordinates[connectorAddress] - const outcomeDimensions = dimensions[connectorAddress] - const isCollapsed = collapsedOutcomes[connectorAddress] - // look for an existing connection that defines a parent - // of this Outcome, so that it can be deleted - // if it is to be changed and a new one added - const hasParent = connections.find( - (connection) => connection.childActionHash === connectorAddress - ) - return ( -
- {outcomeCoordinates && outcomeDimensions && ( - - )} -
- ) - }) + return ( + <> + {connectorAddresses.map((connectorAddress) => { + const outcomeCoordinates = coordinates[connectorAddress] + const outcomeDimensions = dimensions[connectorAddress] + const isCollapsed = collapsedOutcomes[connectorAddress] + // look for an existing connection that defines a parent + // of this Outcome, so that it can be deleted + // if it is to be changed and a new one added + // ONLY do this if there is only one parent + const parents = connections.filter( + (connection) => connection.childActionHash === connectorAddress + ) + const singularParent = parents.length === 1 ? parents[0] : undefined + return ( +
+ {outcomeCoordinates && outcomeDimensions && ( + + )} +
+ ) + })} + + ) } export default OutcomeConnectors diff --git a/web/src/components/OutcomeConnectors/OutcomeConnectors.connector.ts b/web/src/components/OutcomeConnectors/OutcomeConnectors.connector.ts index dbb37aea..f2645d69 100644 --- a/web/src/components/OutcomeConnectors/OutcomeConnectors.connector.ts +++ b/web/src/components/OutcomeConnectors/OutcomeConnectors.connector.ts @@ -1,35 +1,43 @@ import { connect } from 'react-redux' import { RootState } from '../../redux/reducer' import { + OutcomeConnectorFromPayload, setOutcomeConnectorFrom, setOutcomeConnectorTo, } from '../../redux/ephemeral/outcome-connector/actions' -import OutcomeConnectors from './OutcomeConnectors.component' +import OutcomeConnectors, { OutcomeConnectorCommonDispatchProps, OutcomeConnectorsStateProps } from './OutcomeConnectors.component' import { ActionHashB64 } from '../../types/shared' -function mapStateToProps(state: RootState) { +function mapStateToProps(state: RootState): OutcomeConnectorsStateProps { const { ui: { activeProject, viewport: { translate, scale }, layout: { coordinates, dimensions }, hover: { hoveredOutcome: hoveredOutcomeAddress }, - outcomeConnector: { fromAddress, relation, toAddress, validToAddresses, existingParentConnectionAddress }, + outcomeConnector: { + maybeLinkedOutcome, + toAddress, + validToAddresses, + existingParentConnectionAddress, + }, selection: { selectedOutcomes }, }, } = state const connections = state.projects.connections[activeProject] || {} - const projectTags = Object.values(state.projects.tags[activeProject] || {}) const collapsedOutcomes = state.ui.collapsedOutcomes.collapsedOutcomes[activeProject] || {} let connectorAddresses: ActionHashB64[] = [] // only set validToAddresses if we are actually utilizing the connection connector right now - if (fromAddress) { + if (maybeLinkedOutcome) { // connector addresses includes the outcome we are connecting from - // to all the possible outcomes we can connect to validly - connectorAddresses = [fromAddress, ...validToAddresses] + // to all the possible Outcomes we can connect to validly + connectorAddresses = [ + maybeLinkedOutcome.outcomeActionHash, + ...validToAddresses, + ] } - // don't allow the connection connectors when we have multiple outcomes selected + // don't allow the connection connectors when we have multiple Outcomes selected // as it doesn't blend well with the user interface, or make sense at that point else if (hoveredOutcomeAddress && selectedOutcomes.length <= 1) { connectorAddresses = [hoveredOutcomeAddress] @@ -39,7 +47,7 @@ function mapStateToProps(state: RootState) { connectorAddresses = connectorAddresses.filter((address) => { return !Object.values(connections).find((connection) => { return ( - connection.childActionHash === fromAddress && + connection.childActionHash === maybeLinkedOutcome.outcomeActionHash && connection.parentActionHash === address ) }) @@ -47,14 +55,12 @@ function mapStateToProps(state: RootState) { return { activeProject, - projectTags, translate, zoomLevel: scale, coordinates, dimensions, connections: Object.values(connections), // convert from object to array - fromAddress, - relation, + maybeLinkedOutcome, existingParentConnectionAddress, toAddress, connectorAddresses, @@ -62,24 +68,12 @@ function mapStateToProps(state: RootState) { } } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: any): OutcomeConnectorCommonDispatchProps { return { - setOutcomeConnectorFrom: ( - address, - relation, - validToAddresses, - existingParentConnectionAddress - ) => { - return dispatch( - setOutcomeConnectorFrom( - address, - relation, - validToAddresses, - existingParentConnectionAddress - ) - ) + setOutcomeConnectorFrom: (payload: OutcomeConnectorFromPayload) => { + return dispatch(setOutcomeConnectorFrom(payload)) }, - setOutcomeConnectorTo: (address) => { + setOutcomeConnectorTo: (address: ActionHashB64) => { return dispatch(setOutcomeConnectorTo(address)) }, dispatch, diff --git a/web/src/drawing/drawConnection.ts b/web/src/drawing/drawConnection.ts index 37e411f9..4b756ed5 100644 --- a/web/src/drawing/drawConnection.ts +++ b/web/src/drawing/drawConnection.ts @@ -4,10 +4,6 @@ import { SELECTED_COLOR, } from '../styles' import draw from './draw' -import { - RELATION_AS_PARENT, - RELATION_AS_CHILD, -} from '../redux/ephemeral/outcome-connector/actions' import { RelationInput } from '../types' export function calculateConnectionCoordsByOutcomeCoords( @@ -23,13 +19,13 @@ export function calculateConnectionCoordsByOutcomeCoords( parentCoords: { x: number; y: number }, childCoords: { x: number; y: number } - if (relationAs === RELATION_AS_CHILD) { + if (relationAs === RelationInput.ExistingOutcomeAsChild) { childCoords = fromCoords childOutcomeWidth = fromDimensions.width parentCoords = toCoords parentOutcomeWidth = toDimensions.width parentOutcomeHeight = toDimensions.height - } else if (relationAs === RELATION_AS_PARENT) { + } else if (relationAs === RelationInput.ExistingOutcomeAsParent) { childCoords = toCoords childOutcomeWidth = toDimensions.width parentCoords = fromCoords diff --git a/web/src/drawing/eventDetection.ts b/web/src/drawing/eventDetection.ts index d51c7388..92a9a593 100644 --- a/web/src/drawing/eventDetection.ts +++ b/web/src/drawing/eventDetection.ts @@ -4,9 +4,8 @@ import { pathForConnection, } from './drawConnection' import { ActionHashB64 } from '../types/shared' -import { ComputedOutcome } from '../types' +import { ComputedOutcome, RelationInput } from '../types' import { ProjectConnectionsState } from '../redux/persistent/projects/connections/reducer' -import { RELATION_AS_CHILD } from '../redux/ephemeral/outcome-connector/actions' import { CoordinatesState, DimensionsState, @@ -67,7 +66,7 @@ export function checkForConnectionAtCoordinates( childOutcomeDimensions, parentOutcomeCoords, parentOutcomeDimensions, - RELATION_AS_CHILD + RelationInput.ExistingOutcomeAsChild ) const connectionPath = pathForConnection({ fromPoint: childConnectionCoords, diff --git a/web/src/drawing/index.ts b/web/src/drawing/index.ts index 38d97e85..3a505e0b 100644 --- a/web/src/drawing/index.ts +++ b/web/src/drawing/index.ts @@ -12,7 +12,6 @@ import drawConnection, { import drawOverlay from './drawOverlay' import drawSelectBox from './drawSelectBox' import drawEntryPoints from './drawEntryPoints' -import { RELATION_AS_CHILD } from '../redux/ephemeral/outcome-connector/actions' import { getOutcomeHeight, getOutcomeWidth } from './dimensions' import { ComputedOutcome, @@ -202,7 +201,7 @@ function render( allOutcomeDimensions[connection.childActionHash], parentCoords, allOutcomeDimensions[connection.parentActionHash], - RELATION_AS_CHILD + RelationInput.ExistingOutcomeAsChild ) const isHovered = hoveredConnectionActionHash === connection.actionHash const isSelected = selectedConnections.includes(connection.actionHash) diff --git a/web/src/drawing/layoutFormula.ts b/web/src/drawing/layoutFormula.ts index 32cf4407..ffcbc82f 100644 --- a/web/src/drawing/layoutFormula.ts +++ b/web/src/drawing/layoutFormula.ts @@ -1,8 +1,6 @@ import { getOutcomeWidth, getOutcomeHeight } from './dimensions' import { ComputedOutcome, - ComputedScope, - ComputedSimpleAchievementStatus, LayeringAlgorithm, Tag, } from '../types' diff --git a/web/src/event-listeners/index.ts b/web/src/event-listeners/index.ts index 1ebbf4c5..59376534 100644 --- a/web/src/event-listeners/index.ts +++ b/web/src/event-listeners/index.ts @@ -63,8 +63,8 @@ import { triggerUpdateLayout } from '../redux/ephemeral/layout/actions' import { deleteConnection, } from '../redux/persistent/projects/connections/actions' -import { ActionHashB64 } from '../types/shared' -import { ComputedOutcome, RelationInput } from '../types' +import { ActionHashB64, Option } from '../types/shared' +import { ComputedOutcome, LinkedOutcomeDetails, RelationInput } from '../types' import { RootState } from '../redux/reducer' import { findChildrenActionHashes, @@ -100,15 +100,13 @@ function handleMouseUpForOutcomeForm({ state, event, store, - fromAddress, - relation, + maybeLinkedOutcome, existingParentConnectionAddress, }: { state: RootState event: MouseEvent store: any // redux store, for the sake of dispatch - fromAddress?: ActionHashB64 - relation?: RelationInput + maybeLinkedOutcome: Option existingParentConnectionAddress?: ActionHashB64 }) { const calcedPoint = coordsPageToCanvas( @@ -120,15 +118,13 @@ function handleMouseUpForOutcomeForm({ state.ui.viewport.scale ) store.dispatch( - // ASSUMPTION: one parent (existingParentConnectionAddress) - openOutcomeForm( - calcedPoint.x, - calcedPoint.y, - null, - fromAddress, - relation, + openOutcomeForm({ + topConnectionYPosition: calcedPoint.y, + leftConnectionXPosition: calcedPoint.x, + editAddress: null, + maybeLinkedOutcome, existingParentConnectionAddress - ) + }) ) } @@ -480,7 +476,7 @@ export default function setupEventListeners( // if we are using the connection connector // and IMPORTANTLY if Outcome is in the list of `validToAddresses` if ( - state.ui.outcomeConnector.fromAddress && + state.ui.outcomeConnector.maybeLinkedOutcome && state.ui.outcomeConnector.validToAddresses.includes( checks.outcomeActionHash ) @@ -603,25 +599,21 @@ export default function setupEventListeners( } function canvasMouseup(event: MouseEvent) { - const state = store.getState() - // ASSUMPTION: one parent (existingParentConnectionAddress) + const state: RootState = store.getState() const { - fromAddress, - relation, + maybeLinkedOutcome, toAddress, existingParentConnectionAddress, } = state.ui.outcomeConnector const { activeProject } = state.ui - if (fromAddress) { + if (maybeLinkedOutcome) { // covers the case where we are hovered over an Outcome // and thus making a connection to an existing Outcome // AS WELL AS the case where we are not // (to reset the connection connector) handleConnectionConnectMouseUp( - fromAddress, - relation, + maybeLinkedOutcome, toAddress, - // ASSUMPTION: one parent existingParentConnectionAddress, activeProject, store.dispatch @@ -633,8 +625,7 @@ export default function setupEventListeners( state, event, store, - fromAddress, - relation, + maybeLinkedOutcome, existingParentConnectionAddress, }) } @@ -670,7 +661,13 @@ export default function setupEventListeners( translate, scale ) - store.dispatch(openOutcomeForm(canvasPoint.x, canvasPoint.y)) + store.dispatch(openOutcomeForm({ + leftConnectionXPosition: canvasPoint.x, + topConnectionYPosition: canvasPoint.y, + editAddress: null, + maybeLinkedOutcome: null, + existingParentConnectionAddress: null, + })) } } diff --git a/web/src/redux/ephemeral/layout/reducer.ts b/web/src/redux/ephemeral/layout/reducer.ts index bb69fbfd..58fa485c 100644 --- a/web/src/redux/ephemeral/layout/reducer.ts +++ b/web/src/redux/ephemeral/layout/reducer.ts @@ -3,7 +3,7 @@ import { UPDATE_OUTCOME_COORDINATES, UPDATE_OUTCOME_DIMENSIONS, } from './actions' -import { LayoutState } from './state-type' +import { CoordinatesState, DimensionsState, LayoutState } from './state-type' const defaultState: LayoutState = { coordinates: {}, @@ -15,20 +15,16 @@ export default function (state = defaultState, action: any): LayoutState { switch (type) { case UPDATE_OUTCOME_COORDINATES: return { - coordinates: payload.coordinates, - dimensions: { - ...state.dimensions, - }, + ...state, + coordinates: payload.coordinates as CoordinatesState, } case UPDATE_OUTCOME_DIMENSIONS: return { - coordinates: { - ...state.coordinates, - }, - dimensions: payload.dimensions, + ...state, + dimensions: payload.dimensions as DimensionsState, } case UPDATE_LAYOUT: - return payload.layout + return payload.layout as LayoutState default: return state } diff --git a/web/src/redux/ephemeral/outcome-connector/actions.ts b/web/src/redux/ephemeral/outcome-connector/actions.ts index 74db3ac4..a2fdd29e 100644 --- a/web/src/redux/ephemeral/outcome-connector/actions.ts +++ b/web/src/redux/ephemeral/outcome-connector/actions.ts @@ -1,31 +1,20 @@ -import { RelationInput } from '../../../types' -import { ActionHashB64 } from '../../../types/shared' +import { LinkedOutcomeDetails } from '../../../types' +import { ActionHashB64, Option } from '../../../types/shared' const SET_CONNECTION_CONNECTOR_FROM = 'SET_CONNECTION_CONNECTOR_FROM' const SET_CONNECTION_CONNECTOR_TO = 'SET_CONNECTION_CONNECTOR_TO' const RESET_CONNECTION_CONNECTOR = 'RESET_CONNECTION_CONNECTOR' -// these need to match the RelationInput enum -// struct and its serialization design -const RELATION_AS_PARENT = RelationInput.ExistingOutcomeAsParent -const RELATION_AS_CHILD = RelationInput.ExistingOutcomeAsChild +export type OutcomeConnectorFromPayload = { + maybeLinkedOutcome: Option + validToAddresses: ActionHashB64[] + existingParentConnectionAddress: ActionHashB64 +} -// relation should be RELATION_AS_PARENT or RELATION_AS_CHILD -// existingParentConnectionAddress is optional -function setOutcomeConnectorFrom( - actionHash: ActionHashB64, - relation: RelationInput, - validToActionHashes: Array, - existingParentConnectionActionHash: ActionHashB64 -) { +function setOutcomeConnectorFrom(payload: OutcomeConnectorFromPayload) { return { type: SET_CONNECTION_CONNECTOR_FROM, - payload: { - address: actionHash, // TODO: rename - relation, - validToAddresses: validToActionHashes, - existingParentConnectionAddress: existingParentConnectionActionHash, - }, + payload, } } @@ -43,8 +32,6 @@ function resetOutcomeConnector() { } export { - RELATION_AS_PARENT, - RELATION_AS_CHILD, SET_CONNECTION_CONNECTOR_FROM, SET_CONNECTION_CONNECTOR_TO, RESET_CONNECTION_CONNECTOR, diff --git a/web/src/redux/ephemeral/outcome-connector/handler.ts b/web/src/redux/ephemeral/outcome-connector/handler.ts index e82e590a..445298f0 100644 --- a/web/src/redux/ephemeral/outcome-connector/handler.ts +++ b/web/src/redux/ephemeral/outcome-connector/handler.ts @@ -1,15 +1,17 @@ -import { RELATION_AS_PARENT, resetOutcomeConnector } from './actions' -import { createConnection, deleteConnection } from '../../persistent/projects/connections/actions' +import { resetOutcomeConnector } from './actions' +import { + createConnection, + deleteConnection, +} from '../../persistent/projects/connections/actions' import ProjectsZomeApi from '../../../api/projectsApi' import { getAppWs } from '../../../hcWebsockets' import { cellIdFromString } from '../../../utils' import { ActionHashB64 } from '@holochain/client' -import { RelationInput } from '../../../types' -import { CellIdString } from '../../../types/shared' +import { LinkedOutcomeDetails, RelationInput } from '../../../types' +import { CellIdString, Option } from '../../../types/shared' export default async function handleConnectionConnectMouseUp( - fromAddress: ActionHashB64, - relation: RelationInput, + maybeLinkedOutcome: Option, toAddress: ActionHashB64, existingParentConnectionAddress: ActionHashB64, activeProject: CellIdString, @@ -18,28 +20,38 @@ export default async function handleConnectionConnectMouseUp( const cellId = cellIdFromString(activeProject) const appWebsocket = await getAppWs() const projectsZomeApi = new ProjectsZomeApi(appWebsocket) - if (fromAddress && toAddress) { - // if we are replacing an connection with this one - // delete the existing connection first - // ASSUMPTION: one parent + if (maybeLinkedOutcome && toAddress) { + // if we are replacing a Connection with this one + // delete the existing Connection first if (existingParentConnectionAddress) { // don't trigger TRIGGER_LAYOUT_UPDATE here // because we will only be archiving - // an connection here in the context of immediately replacing + // a Connection here in the context of immediately replacing // it with another - await projectsZomeApi.connection.delete(cellId, existingParentConnectionAddress) + await projectsZomeApi.connection.delete( + cellId, + existingParentConnectionAddress + ) dispatch(deleteConnection(activeProject, existingParentConnectionAddress)) } - const fromAsParent = relation === RELATION_AS_PARENT + const fromAsParent = + maybeLinkedOutcome.relation === RelationInput.ExistingOutcomeAsParent const createdConnection = await projectsZomeApi.connection.create(cellId, { - parentActionHash: fromAsParent ? fromAddress : toAddress, - childActionHash: fromAsParent ? toAddress : fromAddress, - siblingOrder: 0, - randomizer: Date.now(), - isImported: false - }) - const createConnectionAction = createConnection(activeProject, createdConnection) + parentActionHash: fromAsParent + ? maybeLinkedOutcome.outcomeActionHash + : toAddress, + childActionHash: fromAsParent + ? toAddress + : maybeLinkedOutcome.outcomeActionHash, + siblingOrder: maybeLinkedOutcome.siblingOrder, + randomizer: Date.now(), + isImported: false, + }) + const createConnectionAction = createConnection( + activeProject, + createdConnection + ) dispatch(createConnectionAction) } dispatch(resetOutcomeConnector()) diff --git a/web/src/redux/ephemeral/outcome-connector/reducer.js b/web/src/redux/ephemeral/outcome-connector/reducer.js deleted file mode 100644 index ca80c1d9..00000000 --- a/web/src/redux/ephemeral/outcome-connector/reducer.js +++ /dev/null @@ -1,43 +0,0 @@ -import { - SET_CONNECTION_CONNECTOR_FROM, - SET_CONNECTION_CONNECTOR_TO, - RESET_CONNECTION_CONNECTOR, -} from './actions' - -const defaultState = { - fromAddress: null, - // RELATION_AS_CHILD or RELATION_AS_PARENT - relation: null, - validToAddresses: [], - toAddress: null, - // existingParentConnectionAddress is the actionHash of the connection that - // we would delete in order to create a new one - // ASSUMPTION: one parent - existingParentConnectionAddress: null -} - -export default function reducer(state = defaultState, action) { - const { payload, type } = action - switch (type) { - case SET_CONNECTION_CONNECTOR_FROM: - return { - ...state, - fromAddress: payload.address, - relation: payload.relation, - validToAddresses: payload.validToAddresses, - // ASSUMPTION: one parent - existingParentConnectionAddress: payload.existingParentConnectionAddress - } - case SET_CONNECTION_CONNECTOR_TO: - return { - ...state, - toAddress: payload, - } - case RESET_CONNECTION_CONNECTOR: - return { - ...defaultState, - } - default: - return state - } -} diff --git a/web/src/redux/ephemeral/outcome-connector/reducer.ts b/web/src/redux/ephemeral/outcome-connector/reducer.ts new file mode 100644 index 00000000..e281fca1 --- /dev/null +++ b/web/src/redux/ephemeral/outcome-connector/reducer.ts @@ -0,0 +1,44 @@ +import { LinkedOutcomeDetails } from '../../../types' +import { ActionHashB64, Option } from '../../../types/shared' +import { + SET_CONNECTION_CONNECTOR_FROM, + SET_CONNECTION_CONNECTOR_TO, + RESET_CONNECTION_CONNECTOR, + OutcomeConnectorFromPayload, +} from './actions' + +export type ConnectionConnectorState = { + maybeLinkedOutcome: Option + validToAddresses: ActionHashB64[] + toAddress: ActionHashB64 + // existingParentConnectionAddress is the actionHash of the Connection that + // we would delete, if any, while creating the new one + existingParentConnectionAddress: ActionHashB64 +} + +const defaultState: ConnectionConnectorState = { + maybeLinkedOutcome: null, + validToAddresses: [], + toAddress: null, + existingParentConnectionAddress: null +} + +export default function reducer(state = defaultState, action: any): ConnectionConnectorState { + const { payload, type } = action + switch (type) { + case SET_CONNECTION_CONNECTOR_FROM: + return { + ...state, + ...payload as OutcomeConnectorFromPayload + } + case SET_CONNECTION_CONNECTOR_TO: + return { + ...state, + toAddress: payload as ActionHashB64, + } + case RESET_CONNECTION_CONNECTOR: + return defaultState + default: + return state + } +} diff --git a/web/src/redux/ephemeral/outcome-form/actions.js b/web/src/redux/ephemeral/outcome-form/actions.js deleted file mode 100644 index c7387cf1..00000000 --- a/web/src/redux/ephemeral/outcome-form/actions.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - There should be an actions.js file in every - feature folder, and it should start with a list - of constants defining all the types of actions - that can be taken within that feature. -*/ - -/* constants */ -const OPEN_OUTCOME_FORM = 'OPEN_OUTCOME_FORM' -const CLOSE_OUTCOME_FORM = 'CLOSE_OUTCOME_FORM' -const UPDATE_CONTENT = 'UPDATE_CONTENT' - -/* action creator functions */ - -// fromAddress and relation are optional -// but should be passed together -// ASSUMPTION: one parent (existingParentConnectionAddress) -function openOutcomeForm(x, y, editAddress, fromAddress, relation, existingParentConnectionAddress) { - return { - type: OPEN_OUTCOME_FORM, - payload: { - editAddress, - x, - y, - fromAddress, - relation, - // ASSUMPTION: one parent (existingParentConnectionAddress) - existingParentConnectionAddress, - }, - } -} - -function closeOutcomeForm() { - return { - type: CLOSE_OUTCOME_FORM, - } -} - -function updateContent(content) { - return { - type: UPDATE_CONTENT, - payload: content, - } -} - -export { - OPEN_OUTCOME_FORM, - CLOSE_OUTCOME_FORM, - UPDATE_CONTENT, - openOutcomeForm, - closeOutcomeForm, - updateContent, -} diff --git a/web/src/redux/ephemeral/outcome-form/actions.ts b/web/src/redux/ephemeral/outcome-form/actions.ts new file mode 100644 index 00000000..14cb08e8 --- /dev/null +++ b/web/src/redux/ephemeral/outcome-form/actions.ts @@ -0,0 +1,46 @@ +import { LinkedOutcomeDetails, RelationInput } from '../../../types' +import { ActionHashB64, Option } from '../../../types/shared' + +/* constants */ +const OPEN_OUTCOME_FORM = 'OPEN_OUTCOME_FORM' +const CLOSE_OUTCOME_FORM = 'CLOSE_OUTCOME_FORM' +const UPDATE_CONTENT = 'UPDATE_CONTENT' + +/* action creator functions */ + +export type OpenOutcomeFormPayload = { + leftConnectionXPosition: number + topConnectionYPosition: number + editAddress: ActionHashB64 + maybeLinkedOutcome: Option + existingParentConnectionAddress: ActionHashB64 +} + +function openOutcomeForm(payload: OpenOutcomeFormPayload) { + return { + type: OPEN_OUTCOME_FORM, + payload, + } +} + +function closeOutcomeForm() { + return { + type: CLOSE_OUTCOME_FORM, + } +} + +function updateContent(content: string) { + return { + type: UPDATE_CONTENT, + payload: content, + } +} + +export { + OPEN_OUTCOME_FORM, + CLOSE_OUTCOME_FORM, + UPDATE_CONTENT, + openOutcomeForm, + closeOutcomeForm, + updateContent, +} diff --git a/web/src/redux/ephemeral/outcome-form/reducer.js b/web/src/redux/ephemeral/outcome-form/reducer.js deleted file mode 100644 index 1238a58c..00000000 --- a/web/src/redux/ephemeral/outcome-form/reducer.js +++ /dev/null @@ -1,66 +0,0 @@ - - -import { OPEN_OUTCOME_FORM, CLOSE_OUTCOME_FORM, UPDATE_CONTENT } from './actions' - -import { DELETE_OUTCOME_FULLY } from '../../persistent/projects/outcomes/actions' - -const defaultState = { - editAddress: null, - content: '', - isOpen: false, - leftConnectionXPosition: 0, - topConnectionYPosition: 0, - // these three go together - // where the fromAddress is the actionHash of the - // Outcome that is the 'origin' of the connection - // and relation indicates the 'port' in a sense, to be parent or child - fromAddress: null, - relation: null, // RELATION_AS_CHILD or RELATION_AS_PARENT - // existingParentConnectionAddress is the actionHash of the connection that - // we would delete in order to create a new one - // ASSUMPTION: one parent - existingParentConnectionAddress: null, // this is optional though -} - -export default function (state = defaultState, action) { - const { payload, type } = action - - const resetVersion = { - ...state, - isOpen: false, - content: '', - editAddress: null, - // these three go together - fromAddress: null, - relation: null, - // ASSUMPTION: one parent - existingParentConnectionAddress: null // this is optional though - } - - switch (type) { - case DELETE_OUTCOME_FULLY: - return resetVersion - case UPDATE_CONTENT: - return { - ...state, - content: payload, - } - case OPEN_OUTCOME_FORM: - return { - ...state, - isOpen: true, - leftConnectionXPosition: payload.x, - topConnectionYPosition: payload.y, - editAddress: payload.editAddress, - // these three go together - fromAddress: payload.fromAddress, - relation: payload.relation, - // ASSUMPTION: one parent - existingParentConnectionAddress: payload.existingParentConnectionAddress, // this is optional though - } - case CLOSE_OUTCOME_FORM: - return resetVersion - default: - return state - } -} diff --git a/web/src/redux/ephemeral/outcome-form/reducer.ts b/web/src/redux/ephemeral/outcome-form/reducer.ts new file mode 100644 index 00000000..39bbff0f --- /dev/null +++ b/web/src/redux/ephemeral/outcome-form/reducer.ts @@ -0,0 +1,65 @@ +import { + OPEN_OUTCOME_FORM, + CLOSE_OUTCOME_FORM, + UPDATE_CONTENT, + OpenOutcomeFormPayload, +} from './actions' + +import { DELETE_OUTCOME_FULLY } from '../../persistent/projects/outcomes/actions' +import { LinkedOutcomeDetails } from '../../../types' +import { Option } from '../../../types/shared' + +export type OutcomeFormState = { + editAddress: string + content: string + isOpen: boolean + leftConnectionXPosition: number + topConnectionYPosition: number + maybeLinkedOutcome: Option + // existingParentConnectionAddress is the actionHash of the connection that + // we would delete, if any, while creating the new one + existingParentConnectionAddress: string +} + +const defaultState: OutcomeFormState = { + editAddress: null, + content: '', + isOpen: false, + leftConnectionXPosition: 0, + topConnectionYPosition: 0, + maybeLinkedOutcome: null, + existingParentConnectionAddress: null, +} + +export default function (state = defaultState, action: any): OutcomeFormState { + const { payload, type } = action + + const resetVersion: OutcomeFormState = { + ...state, + isOpen: false, + content: '', + editAddress: null, + maybeLinkedOutcome: null, + existingParentConnectionAddress: null, + } + + switch (type) { + case DELETE_OUTCOME_FULLY: + return resetVersion + case UPDATE_CONTENT: + return { + ...state, + content: payload, + } + case OPEN_OUTCOME_FORM: + return { + ...state, + ...(payload as OpenOutcomeFormPayload), + isOpen: true, + } + case CLOSE_OUTCOME_FORM: + return resetVersion + default: + return state + } +} diff --git a/web/src/types/createOutcomeWithConnectionInput.ts b/web/src/types/createOutcomeWithConnectionInput.ts index f2fd96ac..7482537e 100644 --- a/web/src/types/createOutcomeWithConnectionInput.ts +++ b/web/src/types/createOutcomeWithConnectionInput.ts @@ -1,4 +1,3 @@ - import { Outcome } from 'zod-models' import { Option, ActionHashB64 } from './shared' @@ -8,8 +7,13 @@ export interface CreateOutcomeWithConnectionInput { } export interface LinkedOutcomeDetails { + // the ActionHashB64 of the + // Outcome that is the 'origin' of the connection outcomeActionHash: ActionHashB64 + // `relation` indicates the 'port' in a sense, to be parent or child relation: RelationInput + // when creating a new Connection, the siblingOrder is the number that + // determines the order of the new Connection relative to its siblings siblingOrder: number } From 5d756dead6883c7d168aa5a5b3a732d236734524 Mon Sep 17 00:00:00 2001 From: Connor Turland <1409121+Connoropolous@users.noreply.github.com> Date: Sat, 23 Sep 2023 15:48:14 -0700 Subject: [PATCH 2/3] broke OutcomeConnectors into three sep. made new Outcomes go to right --- .../OutcomeConnector/OutcomeConnector.scss | 21 ++ .../OutcomeConnector/OutcomeConnector.tsx | 37 +++ .../OutcomeConnectors.component.tsx | 278 ++---------------- .../OutcomeConnectors.connector.ts | 60 ++-- .../OutcomeConnectors/OutcomeConnectors.scss | 20 -- .../SmartOutcomeConnector.scss | 0 .../SmartOutcomeConnector.tsx | 227 ++++++++++++++ web/src/connections/index.ts | 25 +- web/src/drawing/index.ts | 70 +---- web/src/event-listeners/index.ts | 10 +- .../ephemeral/outcome-connector/handler.ts | 5 +- .../ProjectView/MapView/MapView.component.tsx | 5 +- .../ProjectView/MapView/selectRenderProps.ts | 18 +- web/src/stories/OutcomeConnector.stories.tsx | 28 ++ .../stories/SmartOutcomeConnector.stories.tsx | 28 ++ 15 files changed, 463 insertions(+), 369 deletions(-) create mode 100644 web/src/components/OutcomeConnector/OutcomeConnector.scss create mode 100644 web/src/components/OutcomeConnector/OutcomeConnector.tsx create mode 100644 web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.scss create mode 100644 web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.tsx create mode 100644 web/src/stories/OutcomeConnector.stories.tsx create mode 100644 web/src/stories/SmartOutcomeConnector.stories.tsx diff --git a/web/src/components/OutcomeConnector/OutcomeConnector.scss b/web/src/components/OutcomeConnector/OutcomeConnector.scss new file mode 100644 index 00000000..ea9cb472 --- /dev/null +++ b/web/src/components/OutcomeConnector/OutcomeConnector.scss @@ -0,0 +1,21 @@ +.outcome-connector { + position: absolute; + width: 30px; + height: 30px; + transform: translate(-15px, -15px); + background-color: rgba(255, 255, 255, 0.5); + border-radius: 10px; +} + +.outcome-connector-blue-dot { + background-color: #b1b9ff; + width: 10px; + height: 10px; + border-radius: 5px; + margin: 10px; +} + +.outcome-connector:hover .outcome-connector-blue-dot, +.outcome-connector.active .outcome-connector-blue-dot { + background-color: #6772ff; +} \ No newline at end of file diff --git a/web/src/components/OutcomeConnector/OutcomeConnector.tsx b/web/src/components/OutcomeConnector/OutcomeConnector.tsx new file mode 100644 index 00000000..cba19196 --- /dev/null +++ b/web/src/components/OutcomeConnector/OutcomeConnector.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import './OutcomeConnector.scss' + +export type OutcomeConnectorProps = { + active: boolean + pixelTop: number + pixelLeft: number + onMouseDown: (event: React.MouseEvent) => void + onMouseUp: (event: React.MouseEvent) => void + onMouseOver: (event: React.MouseEvent) => void + onMouseOut: (event: React.MouseEvent) => void +} + +const OutcomeConnector: React.FC = ({ + active, + pixelTop, + pixelLeft, + onMouseDown, + onMouseUp, + onMouseOver, + onMouseOut, +}) => { + return ( +
+
+
+ ) +} + +export default OutcomeConnector diff --git a/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx b/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx index 4c24f9aa..893f2c34 100644 --- a/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx +++ b/web/src/components/OutcomeConnectors/OutcomeConnectors.component.tsx @@ -1,293 +1,73 @@ import React from 'react' import './OutcomeConnectors.scss' -import { CONNECTOR_VERTICAL_SPACING } from '../../drawing/dimensions' -import { coordsCanvasToPage } from '../../drawing/coordinateSystems' -import handleConnectionConnectMouseUp from '../../redux/ephemeral/outcome-connector/handler' -import { calculateValidChildren, calculateValidParents } from '../../tree-logic' -import { - ComputedOutcome, - Connection, - LinkedOutcomeDetails, - RelationInput, -} from '../../types' -import { OutcomeConnectorFromPayload } from '../../redux/ephemeral/outcome-connector/actions' -import { - ActionHashB64, - CellIdString, - Option, - WithActionHash, -} from '../../types/shared' -import { ViewportState } from '../../redux/ephemeral/viewport/state-type' +import { ActionHashB64 } from '../../types/shared' import { CoordinatesState, DimensionsState, } from '../../redux/ephemeral/layout/state-type' +import SmartOutcomeConnector, { + SmartOutcomeConnectorStateProps, + SmartOutcomeConnectorDispatchProps, +} from '../SmartOutcomeConnector/SmartOutcomeConnector' -const { ExistingOutcomeAsChild, ExistingOutcomeAsParent } = RelationInput - -export type OutcomeConnectorCommonStateProps = { - activeProject: CellIdString - translate: ViewportState['translate'] - zoomLevel: ViewportState['scale'] - connections: WithActionHash[] - maybeLinkedOutcome: Option - toAddress: ActionHashB64 -} - -export type OutcomeConnectorCommonDispatchProps = { - setOutcomeConnectorFrom: (payload: OutcomeConnectorFromPayload) => void - setOutcomeConnectorTo: (address: ActionHashB64) => void - dispatch: any -} - -// Single -export type OutcomeConnectorProps = OutcomeConnectorCommonStateProps & - OutcomeConnectorCommonDispatchProps & { - ownExistingParentConnectionAddress: ActionHashB64 - presetExistingParentConnectionAddress: ActionHashB64 - address: ActionHashB64 - outcomeCoordinates: CoordinatesState[ActionHashB64] - outcomeDimensions: DimensionsState[ActionHashB64] - isCollapsed: boolean - outcomeActionHashes: ActionHashB64[] - } - -// Plural -export type OutcomeConnectorsStateProps = OutcomeConnectorCommonStateProps & { +// extends SmartOutcomeConnector +export type OutcomeConnectorsStateProps = SmartOutcomeConnectorStateProps & { collapsedOutcomes: { [outcomeActionHash: string]: boolean } coordinates: CoordinatesState dimensions: DimensionsState existingParentConnectionAddress: ActionHashB64 - connectorAddresses: ActionHashB64[] + visibleOutcomesAddresses: ActionHashB64[] } -export type OutcomeConnectorsProps = OutcomeConnectorsStateProps & - OutcomeConnectorCommonDispatchProps & { - outcomes: { - [actionHash: ActionHashB64]: ComputedOutcome - } - } - -const OutcomeConnectorHtml = ({ - active, - pixelTop, - pixelLeft, - onMouseDown, - onMouseUp, - onMouseOver, - onMouseOut, -}) => { - return ( -
-
-
- ) -} - -const OutcomeConnector = ({ - activeProject, - maybeLinkedOutcome, - toAddress, - setOutcomeConnectorFrom, - setOutcomeConnectorTo, - connections, - translate, - zoomLevel, - dispatch, - // specific to this Outcome Connector - ownExistingParentConnectionAddress, - presetExistingParentConnectionAddress, - address, - outcomeCoordinates, - outcomeDimensions, - isCollapsed, - outcomeActionHashes, -}: OutcomeConnectorProps) => { - if (!maybeLinkedOutcome) { - return null - } - const { outcomeActionHash: fromAddress, relation } = maybeLinkedOutcome - - // calculate the coordinates on the page, based - // on what the coordinates on the canvas would be - const { x: topConnectorLeft, y: topConnectorTop } = coordsCanvasToPage( - { - x: outcomeCoordinates.x + outcomeDimensions.width / 2, - y: outcomeCoordinates.y - CONNECTOR_VERTICAL_SPACING, - }, - translate, - zoomLevel - ) - const { x: bottomConnectorLeft, y: bottomConnectorTop } = coordsCanvasToPage( - { - x: outcomeCoordinates.x + outcomeDimensions.width / 2, - y: - outcomeCoordinates.y + - outcomeDimensions.height + - CONNECTOR_VERTICAL_SPACING, - }, - translate, - zoomLevel - ) - - const topConnectorActive = - (address === fromAddress && relation === ExistingOutcomeAsChild) || - (toAddress && address === toAddress && relation === ExistingOutcomeAsParent) - const bottomConnectorActive = - (address === fromAddress && relation === ExistingOutcomeAsParent) || - (toAddress && address === toAddress && relation === ExistingOutcomeAsChild) - // a connection to this upper port would make this Outcome a child of the - // current 'from' Outcome of the connection connector - // if there is one - const canShowTopConnector = - !bottomConnectorActive && - (!relation || relation === ExistingOutcomeAsParent) +// extends SmartOutcomeConnector +export type OutcomeConnectorsDispatchProps = SmartOutcomeConnectorDispatchProps - // a connection to this lower port would make this Outcome a parent of the current 'from' Outcome of the connection connector - // if there is one - const canShowBottomConnector = - !topConnectorActive && - (!relation || relation === ExistingOutcomeAsChild) && - !isCollapsed +export type OutcomeConnectorsProps = OutcomeConnectorsDispatchProps & + OutcomeConnectorsStateProps - // generalized mouse down handler - const connectionConnectMouseDown = ( - direction: RelationInput, - validity: typeof calculateValidParents - ) => (event: React.MouseEvent) => { - if (!fromAddress) { - // if the action is being performed from - // a top port, then there's two options: - // 1. if shiftKey is held, then allow multi-parenting - // 2. if not, then override any existing connection (re-parent) - // if the action is a bottom port, then - // definitely don't override any existing connection - // ASSUMPTION: one parent - const connectionAddressToOverride = - direction === ExistingOutcomeAsChild && !event.shiftKey - ? ownExistingParentConnectionAddress - : undefined - setOutcomeConnectorFrom({ - maybeLinkedOutcome: { - outcomeActionHash: address, - relation: direction, - siblingOrder: 0, // TODO? - }, - existingParentConnectionAddress: connectionAddressToOverride, - validToAddresses: validity(address, connections, outcomeActionHashes), - }) - } - } - - // mouse up handler - const connectionConnectMouseUp = () => { - handleConnectionConnectMouseUp( - maybeLinkedOutcome, - toAddress, - presetExistingParentConnectionAddress, - activeProject, - dispatch - ) - } - // spefific mousedown handlers - const topConnectorOnMouseDown = connectionConnectMouseDown( - ExistingOutcomeAsChild, - calculateValidParents - ) - const bottomConnectorOnMouseDown = connectionConnectMouseDown( - ExistingOutcomeAsParent, - calculateValidChildren - ) - // mouseovers - const connectorOnMouseOver = () => { - // cannot set 'to' the very same Outcome - if (fromAddress && address !== fromAddress) setOutcomeConnectorTo(address) - } - const connectorOnMouseOut = () => { - setOutcomeConnectorTo(null) - } - - return ( - <> - {/* top connector */} - {(canShowTopConnector || topConnectorActive) && ( - - )} - - {/* bottom connector */} - {(canShowBottomConnector || bottomConnectorActive) && ( - - )} - - ) -} - -const OutcomeConnectors = ({ - outcomes, +const OutcomeConnectors: React.FC = ({ + allOutcomeActionHashes, activeProject, translate, zoomLevel, connections, coordinates, dimensions, - maybeLinkedOutcome, + outcomeConnectorMaybeLinkedOutcome, toAddress, existingParentConnectionAddress, - connectorAddresses, + visibleOutcomesAddresses, collapsedOutcomes, setOutcomeConnectorFrom, setOutcomeConnectorTo, dispatch, -}: OutcomeConnectorsProps) => { - // convert from object to array - const outcomeActionHashes = Object.keys(outcomes) +}) => { return ( <> - {connectorAddresses.map((connectorAddress) => { - const outcomeCoordinates = coordinates[connectorAddress] - const outcomeDimensions = dimensions[connectorAddress] - const isCollapsed = collapsedOutcomes[connectorAddress] + {visibleOutcomesAddresses.map((visibleOutcomeAddress) => { + const outcomeCoordinates = coordinates[visibleOutcomeAddress] + const outcomeDimensions = dimensions[visibleOutcomeAddress] + const isCollapsed = collapsedOutcomes[visibleOutcomeAddress] // look for an existing connection that defines a parent // of this Outcome, so that it can be deleted // if it is to be changed and a new one added // ONLY do this if there is only one parent const parents = connections.filter( - (connection) => connection.childActionHash === connectorAddress + (connection) => connection.childActionHash === visibleOutcomeAddress ) const singularParent = parents.length === 1 ? parents[0] : undefined return ( -
+
{outcomeCoordinates && outcomeDimensions && ( - { + return !Object.values(connections).find((connection) => { + return ( + connection.childActionHash === + outcomeConnectorMaybeLinkedOutcome.outcomeActionHash && + connection.parentActionHash === address + ) + }) + }) } - // don't allow the connection connectors when we have multiple Outcomes selected - // as it doesn't blend well with the user interface, or make sense at that point + // show the Connection Connectors for an Outcome which is being hovered over else if (hoveredOutcomeAddress && selectedOutcomes.length <= 1) { - connectorAddresses = [hoveredOutcomeAddress] + // don't allow the connection connectors when we have multiple Outcomes selected + // as it doesn't blend well with the user interface, or make sense at that point + visibleOutcomesAddresses = [hoveredOutcomeAddress] } - // remove any connections that are already in place - connectorAddresses = connectorAddresses.filter((address) => { - return !Object.values(connections).find((connection) => { - return ( - connection.childActionHash === maybeLinkedOutcome.outcomeActionHash && - connection.parentActionHash === address - ) - }) - }) - return { + allOutcomeActionHashes: Object.keys(outcomes), + connections: Object.values(connections), activeProject, translate, zoomLevel: scale, coordinates, dimensions, - connections: Object.values(connections), // convert from object to array - maybeLinkedOutcome, + outcomeConnectorMaybeLinkedOutcome, existingParentConnectionAddress, toAddress, - connectorAddresses, + visibleOutcomesAddresses, collapsedOutcomes, } } -function mapDispatchToProps(dispatch: any): OutcomeConnectorCommonDispatchProps { +function mapDispatchToProps(dispatch: any): SmartOutcomeConnectorDispatchProps { return { setOutcomeConnectorFrom: (payload: OutcomeConnectorFromPayload) => { return dispatch(setOutcomeConnectorFrom(payload)) diff --git a/web/src/components/OutcomeConnectors/OutcomeConnectors.scss b/web/src/components/OutcomeConnectors/OutcomeConnectors.scss index 0e9fd1fd..8b137891 100644 --- a/web/src/components/OutcomeConnectors/OutcomeConnectors.scss +++ b/web/src/components/OutcomeConnectors/OutcomeConnectors.scss @@ -1,21 +1 @@ -.outcome-connector { - position: absolute; - width: 30px; - height: 30px; - transform: translate(-15px, -15px); - background-color: rgba(255, 255, 255, 0.5); - border-radius: 10px; -} -.outcome-connector-blue-dot { - background-color: #b1b9ff; - width: 10px; - height: 10px; - border-radius: 5px; - margin: 10px; -} - -.outcome-connector:hover .outcome-connector-blue-dot, -.outcome-connector.active .outcome-connector-blue-dot { - background-color: #6772ff; -} diff --git a/web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.scss b/web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.scss new file mode 100644 index 00000000..e69de29b diff --git a/web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.tsx b/web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.tsx new file mode 100644 index 00000000..b4f40687 --- /dev/null +++ b/web/src/components/SmartOutcomeConnector/SmartOutcomeConnector.tsx @@ -0,0 +1,227 @@ +import React from 'react' +import { CONNECTOR_VERTICAL_SPACING } from '../../drawing/dimensions' +import { coordsCanvasToPage } from '../../drawing/coordinateSystems' +import handleConnectionConnectMouseUp from '../../redux/ephemeral/outcome-connector/handler' +import { calculateValidChildren, calculateValidParents } from '../../tree-logic' +import { Connection, LinkedOutcomeDetails, RelationInput } from '../../types' +import { OutcomeConnectorFromPayload } from '../../redux/ephemeral/outcome-connector/actions' +import { + ActionHashB64, + CellIdString, + Option, + WithActionHash, +} from '../../types/shared' +import { ViewportState } from '../../redux/ephemeral/viewport/state-type' +import { + CoordinatesState, + DimensionsState, +} from '../../redux/ephemeral/layout/state-type' +import OutcomeConnector from '../OutcomeConnector/OutcomeConnector' +import './SmartOutcomeConnector.scss' +import { lowerThanLowestSiblingOrder } from '../../connections' + +const { ExistingOutcomeAsChild, ExistingOutcomeAsParent } = RelationInput + +export type SmartOutcomeConnectorStateProps = { + activeProject: CellIdString + translate: ViewportState['translate'] + zoomLevel: ViewportState['scale'] + connections: WithActionHash[] + outcomeConnectorMaybeLinkedOutcome: Option + toAddress: ActionHashB64 + allOutcomeActionHashes: ActionHashB64[] +} + +export type SmartOutcomeConnectorDispatchProps = { + setOutcomeConnectorFrom: (payload: OutcomeConnectorFromPayload) => void + setOutcomeConnectorTo: (address: ActionHashB64) => void + dispatch: any +} + +export type SmartOutcomeConnectorProps = SmartOutcomeConnectorStateProps & + SmartOutcomeConnectorDispatchProps & { + ownExistingParentConnectionAddress: ActionHashB64 + presetExistingParentConnectionAddress: ActionHashB64 + outcomeAddress: ActionHashB64 + outcomeCoordinates: CoordinatesState[ActionHashB64] + outcomeDimensions: DimensionsState[ActionHashB64] + isCollapsed: boolean + } + +const SmartOutcomeConnector: React.FC = ({ + activeProject, + // outcomeConnectorMaybeLinkedOutcome indicates + // that a Connection is actively already in the process + // of being made + outcomeConnectorMaybeLinkedOutcome, + toAddress, + setOutcomeConnectorFrom, + setOutcomeConnectorTo, + connections, + translate, + zoomLevel, + dispatch, + // specific to this Outcome Connector + ownExistingParentConnectionAddress, + presetExistingParentConnectionAddress, + outcomeAddress, + outcomeCoordinates, + outcomeDimensions, + isCollapsed, + allOutcomeActionHashes, +}) => { + let fromAddress: Option = null + let relation: Option = null + if (outcomeConnectorMaybeLinkedOutcome) { + fromAddress = outcomeConnectorMaybeLinkedOutcome.outcomeActionHash + relation = outcomeConnectorMaybeLinkedOutcome.relation + } + + // calculate the coordinates on the page, based + // on what the coordinates on the canvas would be + const { x: topConnectorLeft, y: topConnectorTop } = coordsCanvasToPage( + { + x: outcomeCoordinates.x + outcomeDimensions.width / 2, + y: outcomeCoordinates.y - CONNECTOR_VERTICAL_SPACING, + }, + translate, + zoomLevel + ) + const { x: bottomConnectorLeft, y: bottomConnectorTop } = coordsCanvasToPage( + { + x: outcomeCoordinates.x + outcomeDimensions.width / 2, + y: + outcomeCoordinates.y + + outcomeDimensions.height + + CONNECTOR_VERTICAL_SPACING, + }, + translate, + zoomLevel + ) + + const topConnectorActive = + (outcomeAddress === fromAddress && relation === ExistingOutcomeAsChild) || + (toAddress && + outcomeAddress === toAddress && + relation === ExistingOutcomeAsParent) + const bottomConnectorActive = + (outcomeAddress === fromAddress && relation === ExistingOutcomeAsParent) || + (toAddress && + outcomeAddress === toAddress && + relation === ExistingOutcomeAsChild) + + // a connection to this upper port would make this Outcome a child of the + // current 'from' Outcome of the connection connector + // if there is one + const canShowTopConnector = + !bottomConnectorActive && + (!relation || relation === ExistingOutcomeAsParent) + + // a connection to this lower port would make this Outcome a parent of the current 'from' Outcome of the connection connector + // if there is one + const canShowBottomConnector = + !topConnectorActive && + (!relation || relation === ExistingOutcomeAsChild) && + !isCollapsed + + // generalized mouse down handler + const connectionConnectMouseDown = ( + direction: RelationInput, + validity: typeof calculateValidParents + ) => (event: React.MouseEvent) => { + if (!fromAddress) { + // IF the action is being performed from + // a top port, then there's two options: + // 1. if shiftKey is held, then allow multi-parenting + // 2. if not, then override any existing connection (re-parent) + // IF the action is a bottom port, then + // definitely don't override any existing connection + const connectionAddressToOverride = + direction === ExistingOutcomeAsChild && !event.shiftKey + ? ownExistingParentConnectionAddress + : undefined + // when making a new sibling, we want it to go to the far right. + // we do this by setting a siblingOrder lower than the current lowest + const siblingOrder = ExistingOutcomeAsParent + ? lowerThanLowestSiblingOrder({ + connections, + parentActionHash: outcomeAddress, + }) + : 0 + setOutcomeConnectorFrom({ + maybeLinkedOutcome: { + outcomeActionHash: outcomeAddress, + relation: direction, + siblingOrder, + }, + existingParentConnectionAddress: connectionAddressToOverride, + validToAddresses: validity( + outcomeAddress, + connections, + allOutcomeActionHashes + ), + }) + } + } + + // mouse up handler + const connectionConnectMouseUp = () => { + handleConnectionConnectMouseUp( + outcomeConnectorMaybeLinkedOutcome, + toAddress, + presetExistingParentConnectionAddress, + activeProject, + dispatch + ) + } + // spefific mousedown handlers + const topConnectorOnMouseDown = connectionConnectMouseDown( + ExistingOutcomeAsChild, + calculateValidParents + ) + const bottomConnectorOnMouseDown = connectionConnectMouseDown( + ExistingOutcomeAsParent, + calculateValidChildren + ) + // mouseovers + const connectorOnMouseOver = () => { + // cannot set 'to' the very same Outcome + if (fromAddress && outcomeAddress !== fromAddress) + setOutcomeConnectorTo(outcomeAddress) + } + const connectorOnMouseOut = () => { + setOutcomeConnectorTo(null) + } + + return ( + <> + {/* top connector */} + {(canShowTopConnector || topConnectorActive) && ( + + )} + + {/* bottom connector */} + {(canShowBottomConnector || bottomConnectorActive) && ( + + )} + + ) +} + +export default SmartOutcomeConnector diff --git a/web/src/connections/index.ts b/web/src/connections/index.ts index cd0930ac..b8dfa135 100644 --- a/web/src/connections/index.ts +++ b/web/src/connections/index.ts @@ -6,6 +6,8 @@ import { updateConnection } from '../redux/persistent/projects/connections/actio import { getAppWs } from '../hcWebsockets' import ProjectsZomeApi from '../api/projectsApi' import { cellIdFromString } from '../utils' +import { WithActionHash } from '../types/shared' +import { Connection } from 'zod-models' export async function alterSiblingOrder( store: any, @@ -84,10 +86,31 @@ export async function alterSiblingOrder( ) // we skip the layout animation on the first of the two // since we don't want to redundantly animate the layout - store.dispatch(updateConnection(activeProject, updatedSelectedConnection, true)) + store.dispatch( + updateConnection(activeProject, updatedSelectedConnection, true) + ) // we do not skip the layout animation on the second of the two store.dispatch(updateConnection(activeProject, updatedTargetConnection)) } else { console.log('could not find connections') } } + +export function lowerThanLowestSiblingOrder({ + connections, + parentActionHash, +}: { + connections: WithActionHash[] + parentActionHash: ActionHashB64 +}) { + // find all the existing children of the parent + const children = connections.filter((connection) => { + return connection.parentActionHash === parentActionHash + }) + // find the lowest siblingOrder of the children + const lowestSiblingOrder = children.reduce((lowest, connection) => { + return connection.siblingOrder < lowest ? connection.siblingOrder : lowest + }, Infinity) + // return a lower siblingOrder than the lowest one + return lowestSiblingOrder === Infinity ? 0 : lowestSiblingOrder - 1 +} diff --git a/web/src/drawing/index.ts b/web/src/drawing/index.ts index 3a505e0b..4ac9e0a3 100644 --- a/web/src/drawing/index.ts +++ b/web/src/drawing/index.ts @@ -29,6 +29,8 @@ import { CoordinatesState, DimensionsState, } from '../redux/ephemeral/layout/state-type' +import selectRenderProps from '../routes/ProjectView/MapView/selectRenderProps' +import { ProjectComputedOutcomes } from '../context/ComputedOutcomeContext' function setupCanvas(canvas) { // Get the device pixel ratio, falling back to 1. @@ -43,52 +45,6 @@ function setupCanvas(canvas) { return ctx } -export type renderProps = { - projectTags: WithActionHash[] - screenWidth: number - screenHeight: number - zoomLevel: number - translate: { - x: number - y: number - } - activeEntryPoints: ActionHashB64[] - projectMeta: WithActionHash - entryPoints: ProjectEntryPointsState - outcomeMembers: ProjectOutcomeMembersState - connections: ProjectConnectionsState - outcomeConnectorFromAddress: ActionHashB64 - outcomeConnectorToAddress: ActionHashB64 - outcomeConnectorRelation: RelationInput - outcomeConnectorExistingParent: ActionHashB64 - outcomeFormIsOpen: boolean - outcomeFormFromActionHash: ActionHashB64 - outcomeFormContent: string - outcomeFormRelation: RelationInput - outcomeFormLeftConnectionX: number - outcomeFormTopConnectionY: number - outcomeFormExistingParent: ActionHashB64 - hoveredConnectionActionHash: ActionHashB64 - selectedConnections: ActionHashB64[] - selectedOutcomes: ActionHashB64[] - mouseLiveCoordinate: { - x: number - y: number - } - shiftKeyDown: boolean - startedSelection: boolean - startedSelectionCoordinate: { - x: number - y: number - } - coordinates: CoordinatesState - dimensions: DimensionsState - computedOutcomesKeyed: { - [outcomeActionHash: ActionHashB64]: ComputedOutcome - } - computedOutcomesAsTree: ComputedOutcome[] -} - // Render is responsible for painting all the existing outcomes & connections, // as well as the yet to be created (pending) ones (For new Outcome / new Connection / edit Connection) // render the state contained in store onto the canvas @@ -96,6 +52,8 @@ export type renderProps = { // `canvas` is a reference to an HTML5 canvas DOM element function render( { + computedOutcomesKeyed, + // from selectRenderProps projectTags, screenWidth, screenHeight, @@ -104,19 +62,15 @@ function render( dimensions: allOutcomeDimensions, translate, activeEntryPoints, - computedOutcomesKeyed, - computedOutcomesAsTree, connections, outcomeMembers, entryPoints, projectMeta, - outcomeConnectorFromAddress, + outcomeConnectorMaybeLinkedOutcome, outcomeConnectorToAddress, - outcomeConnectorRelation, outcomeConnectorExistingParent, outcomeFormIsOpen, - outcomeFormFromActionHash, - outcomeFormRelation, + outcomeFormMaybeLinkedOutcome, outcomeFormContent, outcomeFormLeftConnectionX, outcomeFormTopConnectionY, @@ -128,7 +82,9 @@ function render( shiftKeyDown, startedSelection, startedSelectionCoordinate, - }: renderProps, + }: ReturnType & { + computedOutcomesKeyed: ProjectComputedOutcomes['computedOutcomesKeyed'] + }, canvas: HTMLCanvasElement ) { // Get the 2 dimensional drawing context of the canvas (there is also 3 dimensional, e.g.) @@ -425,7 +381,9 @@ function render( */ // render the connection that is pending to be created to the open outcome form - if (outcomeFormIsOpen && outcomeFormFromActionHash) { + if (outcomeFormIsOpen && outcomeFormMaybeLinkedOutcome) { + const outcomeFormFromActionHash = outcomeFormMaybeLinkedOutcome.outcomeActionHash + const outcomeFormRelation = outcomeFormMaybeLinkedOutcome.relation const [ connection1port, connection2port, @@ -458,7 +416,9 @@ function render( // as being "to", then we will be drawing the connection to its correct // upper or lower port // the opposite of whichever the "from" port is connected to - if (outcomeConnectorFromAddress) { + if (outcomeConnectorMaybeLinkedOutcome) { + const outcomeConnectorFromAddress = outcomeConnectorMaybeLinkedOutcome.outcomeActionHash + const outcomeConnectorRelation = outcomeConnectorMaybeLinkedOutcome.relation const fromCoords = coordinates[outcomeConnectorFromAddress] const [ childCoords, diff --git a/web/src/event-listeners/index.ts b/web/src/event-listeners/index.ts index 59376534..b4593b9e 100644 --- a/web/src/event-listeners/index.ts +++ b/web/src/event-listeners/index.ts @@ -55,7 +55,6 @@ import { resetOutcomeConnector, setOutcomeConnectorTo, } from '../redux/ephemeral/outcome-connector/actions' -import handleConnectionConnectMouseUp from '../redux/ephemeral/outcome-connector/handler' import ProjectsZomeApi from '../api/projectsApi' import { getAppWs } from '../hcWebsockets' import { cellIdFromString } from '../utils' @@ -64,7 +63,7 @@ import { deleteConnection, } from '../redux/persistent/projects/connections/actions' import { ActionHashB64, Option } from '../types/shared' -import { ComputedOutcome, LinkedOutcomeDetails, RelationInput } from '../types' +import { ComputedOutcome, LinkedOutcomeDetails } from '../types' import { RootState } from '../redux/reducer' import { findChildrenActionHashes, @@ -84,6 +83,7 @@ import { import { alterSiblingOrder, } from '../connections' +import handleOutcomeConnectorMouseUp from '../redux/ephemeral/outcome-connector/handler' // The "modifier" key is different on Mac and non-Mac // Pattern borrowed from TinyKeys library. @@ -606,12 +606,14 @@ export default function setupEventListeners( existingParentConnectionAddress, } = state.ui.outcomeConnector const { activeProject } = state.ui + + // if we are using the Connection Connector if (maybeLinkedOutcome) { // covers the case where we are hovered over an Outcome // and thus making a connection to an existing Outcome // AS WELL AS the case where we are not // (to reset the connection connector) - handleConnectionConnectMouseUp( + handleOutcomeConnectorMouseUp( maybeLinkedOutcome, toAddress, existingParentConnectionAddress, @@ -621,6 +623,8 @@ export default function setupEventListeners( // covers the case where we are not hovered over an Outcome // and thus making a new Outcome and connection/Connection if (!toAddress) { + // here we transfer the `maybeLinkedOutcome` from the Outcome Connector + // state over to the Outcome Form state handleMouseUpForOutcomeForm({ state, event, diff --git a/web/src/redux/ephemeral/outcome-connector/handler.ts b/web/src/redux/ephemeral/outcome-connector/handler.ts index 445298f0..2f075710 100644 --- a/web/src/redux/ephemeral/outcome-connector/handler.ts +++ b/web/src/redux/ephemeral/outcome-connector/handler.ts @@ -10,7 +10,7 @@ import { ActionHashB64 } from '@holochain/client' import { LinkedOutcomeDetails, RelationInput } from '../../../types' import { CellIdString, Option } from '../../../types/shared' -export default async function handleConnectionConnectMouseUp( +export default async function handleOutcomeConnectorMouseUp( maybeLinkedOutcome: Option, toAddress: ActionHashB64, existingParentConnectionAddress: ActionHashB64, @@ -37,6 +37,8 @@ export default async function handleConnectionConnectMouseUp( const fromAsParent = maybeLinkedOutcome.relation === RelationInput.ExistingOutcomeAsParent + // basing off of the 'relation' we establish which Outcome + // is the parent and which is the child const createdConnection = await projectsZomeApi.connection.create(cellId, { parentActionHash: fromAsParent ? maybeLinkedOutcome.outcomeActionHash @@ -54,5 +56,6 @@ export default async function handleConnectionConnectMouseUp( ) dispatch(createConnectionAction) } + // always reset the Outcome Connector dispatch(resetOutcomeConnector()) } diff --git a/web/src/routes/ProjectView/MapView/MapView.component.tsx b/web/src/routes/ProjectView/MapView/MapView.component.tsx index b1a89363..90790885 100644 --- a/web/src/routes/ProjectView/MapView/MapView.component.tsx +++ b/web/src/routes/ProjectView/MapView/MapView.component.tsx @@ -126,12 +126,11 @@ const MapView: React.FC = ({ { ...renderProps, computedOutcomesKeyed, - computedOutcomesAsTree, }, canvas ) } - }, [renderProps, projectId, computedOutcomesAsTree, computedOutcomesKeyed]) + }, [renderProps, projectId, computedOutcomesKeyed]) const transform = { transform: `matrix(${zoomLevel}, 0, 0, ${zoomLevel}, ${translate.x}, ${translate.y})`, @@ -217,7 +216,7 @@ const MapView: React.FC = ({ {/* Outcome Connectors */} {/* an undefined value of refCanvas.current was causing a crash, due to canvas prop being undefined */} {refCanvas.current && zoomLevel >= 0.12 && !contextMenuCoordinate && ( - + )} {/* CollapsedChildrenPills */} {/* an undefined value of refCanvas.current was causing a crash, due to canvas prop being undefined */} diff --git a/web/src/routes/ProjectView/MapView/selectRenderProps.ts b/web/src/routes/ProjectView/MapView/selectRenderProps.ts index 39db9a49..bfe2a444 100644 --- a/web/src/routes/ProjectView/MapView/selectRenderProps.ts +++ b/web/src/routes/ProjectView/MapView/selectRenderProps.ts @@ -16,12 +16,10 @@ const selectRenderProps = createSelector( (state: RootState) => state.projects.entryPoints[state.ui.activeProject], (state: RootState) => state.projects.outcomeMembers[state.ui.activeProject], (state: RootState) => state.projects.connections[state.ui.activeProject], - (state: RootState) => state.ui.outcomeConnector.fromAddress, + (state: RootState) => state.ui.outcomeConnector.maybeLinkedOutcome, (state: RootState) => state.ui.outcomeConnector.toAddress, - (state: RootState) => state.ui.outcomeConnector.relation, (state: RootState) => state.ui.outcomeConnector.existingParentConnectionAddress, - (state: RootState) => state.ui.outcomeForm.fromAddress, - (state: RootState) => state.ui.outcomeForm.relation, + (state: RootState) => state.ui.outcomeForm.maybeLinkedOutcome, (state: RootState) => state.ui.outcomeForm.content, (state: RootState) => state.ui.outcomeForm.leftConnectionXPosition, (state: RootState) => state.ui.outcomeForm.topConnectionYPosition, @@ -47,12 +45,10 @@ const selectRenderProps = createSelector( entryPoints, outcomeMembers, connections, - outcomeConnectorFromAddress, + outcomeConnectorMaybeLinkedOutcome, outcomeConnectorToAddress, - outcomeConnectorRelation, outcomeConnectorExistingParent, - outcomeFormFromActionHash, - outcomeFormRelation, + outcomeFormMaybeLinkedOutcome, outcomeFormContent, outcomeFormLeftConnectionX, outcomeFormTopConnectionY, @@ -78,13 +74,11 @@ const selectRenderProps = createSelector( entryPoints, outcomeMembers, connections, - outcomeConnectorFromAddress, + outcomeConnectorMaybeLinkedOutcome, outcomeConnectorToAddress, - outcomeConnectorRelation, outcomeConnectorExistingParent, outcomeFormIsOpen, - outcomeFormFromActionHash, - outcomeFormRelation, + outcomeFormMaybeLinkedOutcome, outcomeFormContent, outcomeFormLeftConnectionX, outcomeFormTopConnectionY, diff --git a/web/src/stories/OutcomeConnector.stories.tsx b/web/src/stories/OutcomeConnector.stories.tsx new file mode 100644 index 00000000..069b7c84 --- /dev/null +++ b/web/src/stories/OutcomeConnector.stories.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import '../variables.scss' + +import OutcomeConnectorComponent, { + OutcomeConnectorProps, +} from '../components/OutcomeConnector/OutcomeConnector' + +// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default { + title: 'Example/OutcomeConnector', + component: OutcomeConnectorComponent, +} as ComponentMeta + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const Template: ComponentStory = (args) => { + return +} + +export const OutcomeConnector = Template.bind({}) + +// 'single story hoist' (place the component at the 'top level' without nesting in the storybook menu) +OutcomeConnector.storyName = 'OutcomeConnector' +// More on args: https://storybook.js.org/docs/react/writing-stories/args +const args: OutcomeConnectorProps = { + // assign props here +} +OutcomeConnector.args = args diff --git a/web/src/stories/SmartOutcomeConnector.stories.tsx b/web/src/stories/SmartOutcomeConnector.stories.tsx new file mode 100644 index 00000000..1a87276e --- /dev/null +++ b/web/src/stories/SmartOutcomeConnector.stories.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import '../variables.scss' + +import SmartOutcomeConnectorComponent, { + SmartOutcomeConnectorProps, +} from '../components/SmartOutcomeConnector/SmartOutcomeConnector' + +// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default { + title: 'Example/SmartOutcomeConnector', + component: SmartOutcomeConnectorComponent, +} as ComponentMeta + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const Template: ComponentStory = (args) => { + return +} + +export const SmartOutcomeConnector = Template.bind({}) + +// 'single story hoist' (place the component at the 'top level' without nesting in the storybook menu) +SmartOutcomeConnector.storyName = 'SmartOutcomeConnector' +// More on args: https://storybook.js.org/docs/react/writing-stories/args +const args: SmartOutcomeConnectorProps = { + // assign props here +} +SmartOutcomeConnector.args = args From 1cbbf1934d8a36553f2e3443c44e668408a50496 Mon Sep 17 00:00:00 2001 From: Connor Turland <1409121+Connoropolous@users.noreply.github.com> Date: Sat, 23 Sep 2023 15:57:23 -0700 Subject: [PATCH 3/3] 9.3.1-alpha --- .github/workflows/release.yml | 6 +++--- README.md | 2 +- electron/package-lock.json | 4 ++-- electron/package.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- web/dist/splashscreen.html | 2 +- web/package-lock.json | 4 ++-- web/package.json | 2 +- web/src/splashscreen.html | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72b92a22..d67d1b1d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,16 +59,16 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release upload "${GITHUB_REF#refs/tags/}" "electron/out/Acorn-9.2.1-alpha.AppImage" --clobber + gh release upload "${GITHUB_REF#refs/tags/}" "electron/out/Acorn-9.3.1-alpha.AppImage" --clobber - name: upload binary (macos only) if: ${{ runner.os == 'macOs' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release upload "${GITHUB_REF#refs/tags/}" "electron/out/Acorn.9.2.1-alpha.darwin-x64.zip" --clobber + gh release upload "${GITHUB_REF#refs/tags/}" "electron/out/Acorn.9.3.1-alpha.darwin-x64.zip" --clobber - name: upload binary (Windows only) if: ${{ runner.os == 'Windows' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - gh release upload "$($env:GITHUB_REF -replace "refs/tags/")" "electron/out/Acorn.Setup.9.2.1-alpha.exe" --clobber + gh release upload "$($env:GITHUB_REF -replace "refs/tags/")" "electron/out/Acorn.Setup.9.3.1-alpha.exe" --clobber diff --git a/README.md b/README.md index 792925a7..6555875a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Acorn is currently in **Alpha** testing phase. ## Things you can do -- [**Download & install the latest Acorn release**](https://github.com/lightningrodlabs/acorn/releases/tag/v9.2.1-alpha). +- [**Download & install the latest Acorn release**](https://github.com/lightningrodlabs/acorn/releases/tag/v9.3.1-alpha). - Check out the [Acorn Knowledge Base](https://docs.acorn.software) to learn more about Acorn, its methodology and features. diff --git a/electron/package-lock.json b/electron/package-lock.json index b6dbaac2..768cf3f6 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "acorn", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "acorn", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "license": "CAL-1.0", "dependencies": { "@lightningrodlabs/electron-holochain": "=0.7.8", diff --git a/electron/package.json b/electron/package.json index 939e945e..0e6c830d 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "acorn", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "description": "Open source peer-to-peer project management for software teams", "main": "dist/index.js", "scripts": { diff --git a/package-lock.json b/package-lock.json index 7b401926..f10cf4ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "acorn", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "acorn", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "hasInstallScript": true, "license": "CAL-1.0", "dependencies": { diff --git a/package.json b/package.json index 542c6eed..d1e454ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "acorn", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "description": "Acorn is software that helps people create the future", "repository": { "type": "git", diff --git a/web/dist/splashscreen.html b/web/dist/splashscreen.html index 60819f95..23addc12 100644 --- a/web/dist/splashscreen.html +++ b/web/dist/splashscreen.html @@ -1,4 +1,4 @@ -Acorn
Photograph by Valeriia Miller
version 9.2.1-alpha
Setting up Holochain...
Acorn is an open-source, peer-to-peer project management application designed and built for distributed software development teams. Acorn functions through defining Intended Outcomes for a project in a Dependency Tree structure.

Acorn is built as a Holochain application, meaning it runs on decentralized peer-to-peer computing and can be used without server infrastructure or a hosting service. The users of a particular Acorn instance are its hosting power.
© 2020-2023 Harris-Braun Enterprises, LLC.
Licensed under the Cryptographic Autonomy License v1.0.
Photograph by Valeriia Miller
version 9.3.1-alpha
Setting up Holochain...
Acorn is an open-source, peer-to-peer project management application designed and built for distributed software development teams. Acorn functions through defining Intended Outcomes for a project in a Dependency Tree structure.

Acorn is built as a Holochain application, meaning it runs on decentralized peer-to-peer computing and can be used without server infrastructure or a hosting service. The users of a particular Acorn instance are its hosting power.
© 2020-2023 Harris-Braun Enterprises, LLC.
Licensed under the Cryptographic Autonomy License v1.0.
\ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 9bf032c4..37d812cc 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "acorn-ui", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "acorn-ui", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "dependencies": { "@babel/runtime": "^7.7.4", "@holochain/client": "0.16.1", diff --git a/web/package.json b/web/package.json index 356d3e1a..d374d91d 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "acorn-ui", - "version": "9.2.1-alpha", + "version": "9.3.1-alpha", "browser": { "child_process": false }, diff --git a/web/src/splashscreen.html b/web/src/splashscreen.html index 95c658b2..44534927 100644 --- a/web/src/splashscreen.html +++ b/web/src/splashscreen.html @@ -12,7 +12,7 @@
-
version 9.2.1-alpha
+
version 9.3.1-alpha
Setting up Holochain...