Skip to content

Commit

Permalink
Resolution outcome transitions
Browse files Browse the repository at this point in the history
  • Loading branch information
ekuiter committed Jun 11, 2019
1 parent 884585d commit 5f7f755
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 44 deletions.
3 changes: 2 additions & 1 deletion client/src/components/CommandBarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const CommandBarContainer = (props: StateDerivedProps & RouteProps) => (
commands.featureDiagram.setLayout(
props.featureDiagramLayout!,
props.onSetFeatureDiagramLayout!),
...enableConstraintsView(props.featureModel)
...enableConstraintsView(props.featureModel, props.transitionConflictDescriptor)
? [makeDivider(),
commands.featureDiagram.showConstraintView(
props.onSetSetting!, props.settings!.views.splitAt),
Expand Down Expand Up @@ -138,6 +138,7 @@ export default withRouter(connect(
isSelectMultipleFeatures: collaborativeSession.isSelectMultipleFeatures,
selectedFeatureIDs: collaborativeSession.selectedFeatureIDs,
collaborators: collaborativeSession.collaborators,
transitionConflictDescriptor: collaborativeSession.transitionConflictDescriptor,
featureModel: getCurrentFeatureModel(state),
currentArtifactPath: collaborativeSession.artifactPath
};
Expand Down
42 changes: 24 additions & 18 deletions client/src/components/FeatureDiagramRouteContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,30 @@ class FeatureDiagramRoute extends React.Component<FeatureDiagramRouteProps> {
settings={this.props.settings!}
onSetSetting={this.props.onSetSetting!}
renderPrimaryView={(style: CSSProperties) =>
this.props.featureModel
? <FeatureDiagramView
featureDiagramLayout={this.props.featureDiagramLayout!}
currentArtifactPath={this.props.currentArtifactPath!}
this.props.transitionConflictDescriptor || this.props.conflictDescriptor
? <ConflictView
conflictDescriptor={(this.props.transitionConflictDescriptor || this.props.conflictDescriptor)!}
myself={this.props.myself!}
collaborators={this.props.collaborators!}
voterSiteIDs={this.props.voterSiteIDs}
votes={this.props.votes!}
onVote={this.props.onVote!}
settings={this.props.settings!}
{...this.props}
style={style}/>
: this.props.conflictDescriptor
? <ConflictView
conflictDescriptor={this.props.conflictDescriptor}
myself={this.props.myself!}
collaborators={this.props.collaborators!}
voterSiteIDs={this.props.voterSiteIDs}
votes={this.props.votes!}
onVote={this.props.onVote!}
settings={this.props.settings!}/>
transitioning={!!this.props.transitionConflictDescriptor}
transitionResolutionOutcome={this.props.transitionResolutionOutcome}
onEndConflictViewTransition={this.props.onEndConflictTransition!}/>
: this.props.featureModel
? <FeatureDiagramView
featureDiagramLayout={this.props.featureDiagramLayout!}
currentArtifactPath={this.props.currentArtifactPath!}
settings={this.props.settings!}
{...this.props}
style={style}/>
: <div style={{display: 'flex'}}>
<Spinner size={SpinnerSize.large}/>
</div>}
</div>}
renderSecondaryView={() => <ConstraintsView featureModel={this.props.featureModel!}/>}
enableSecondaryView={() => enableConstraintsView(this.props.featureModel)}/>;
enableSecondaryView={() => enableConstraintsView(this.props.featureModel, this.props.transitionConflictDescriptor)}/>;
}
}

Expand All @@ -78,6 +81,8 @@ export default withRouter(connect(
...props,
featureModel: getCurrentFeatureModel(state),
conflictDescriptor: getCurrentConflictDescriptor(state),
transitionResolutionOutcome: collaborativeSession.transitionResolutionOutcome,
transitionConflictDescriptor: collaborativeSession.transitionConflictDescriptor,
currentArtifactPath: collaborativeSession.artifactPath,
featureDiagramLayout: collaborativeSession.layout,
isSelectMultipleFeatures: collaborativeSession.isSelectMultipleFeatures,
Expand All @@ -99,6 +104,7 @@ export default withRouter(connect(
onDeselectAllFeatures: () => dispatch(actions.ui.featureDiagram.feature.deselectAll()),
onToggleFeatureGroupType: payload => dispatch<any>(actions.server.featureDiagram.feature.properties.toggleGroup(payload)),
onToggleFeatureOptional: payload => dispatch<any>(actions.server.featureDiagram.feature.properties.toggleOptional(payload)),
onVote: payload => dispatch<any>(actions.server.featureDiagram.vote(payload))
onVote: payload => dispatch<any>(actions.server.featureDiagram.vote(payload)),
onEndConflictTransition: () => dispatch(actions.ui.endConflictViewTransition())
})
)(FeatureDiagramRoute));
27 changes: 21 additions & 6 deletions client/src/components/conflictView/ConflictView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {present} from '../../helpers/present';
import {Settings} from '../../store/settings';
import {PersonaSize} from 'office-ui-fabric-react/lib/Persona';
import Version from './Version';
import constants from '../../constants';

interface Props {
conflictDescriptor: KernelConflictDescriptor,
Expand All @@ -18,7 +19,10 @@ interface Props {
voterSiteIDs?: string[],
votes: Votes,
onVote: OnVoteFunction,
settings: Settings
settings: Settings,
transitioning: boolean,
transitionResolutionOutcome?: string,
onEndConflictViewTransition: () => void
};

interface State {
Expand All @@ -42,8 +46,17 @@ export default class extends React.Component<Props> {
.map(([siteID, _]) => this.props.collaborators.find(collaborator => collaborator.siteID === siteID))
.filter(present);

componentDidUpdate(prevProps: Props) {
if (!prevProps.transitioning && this.props.transitioning)
window.setTimeout(this.props.onEndConflictViewTransition,
this.props.transitionResolutionOutcome === 'neutral'
? constants.featureDiagram.conflictView.transitionNeutral
: constants.featureDiagram.conflictView.transition);
}

render(): JSX.Element {
const {conflictDescriptor, myself, collaborators, voterSiteIDs, settings} = this.props,
const {conflictDescriptor, myself, collaborators, voterSiteIDs, settings,
transitioning, transitionResolutionOutcome} = this.props,
synchronized = conflictDescriptor.synchronized,
pendingVotePermission = synchronized && !voterSiteIDs,
allowedToVote = synchronized && !pendingVotePermission && voterSiteIDs!.includes(myself.siteID),
Expand All @@ -54,7 +67,7 @@ export default class extends React.Component<Props> {
<div className="conflict">
<div className="header">
<div className="heading">{i18n.t('conflictResolution.header')}</div>&nbsp;&nbsp;
{(!synchronized || pendingVotePermission || disallowedToVote) &&
{!transitioning && (!synchronized || pendingVotePermission || disallowedToVote) &&
<div className="info">
{(!synchronized || pendingVotePermission) &&
<Spinner size={SpinnerSize.small}/>}
Expand Down Expand Up @@ -88,16 +101,18 @@ export default class extends React.Component<Props> {
ownVotedVersionID={this.ownVotedVersionID()}
versionID={versionID}
versionIndex={versionIndex}
activeOperationID={this.state.activeOperationID}
activeOperationID={!transitioning ? this.state.activeOperationID : undefined}
onSetActiveOperationID={this.onSetActiveOperationID}
activeVersionID={this.state.activeVersionID}
activeVersionID={!transitioning ? this.state.activeVersionID : undefined}
onSetActiveVersionID={this.onSetActiveVersionID}
myself={myself}
collaborators={collaborators}
collaboratorsInFavor={this.collaboratorsInFavor(versionID)}
settings={settings}
onVote={this.onVote}
allowedToVote={allowedToVote}/>)}
allowedToVote={allowedToVote}
transitioning={transitioning}
transitionResolutionOutcome={transitionResolutionOutcome}/>)}
</div>
</div>
);
Expand Down
7 changes: 4 additions & 3 deletions client/src/components/conflictView/Operation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ interface Props {
onSetActiveOperationID: (activeOperationID?: string) => void,
activeVersionID?: string,
myself?: Collaborator,
collaborators: Collaborator[]
collaborators: Collaborator[],
transitioning: boolean
};

interface State {
Expand All @@ -37,7 +38,7 @@ export default class extends React.Component<Props, State> {

render() {
const {conflictDescriptor, versionID, operationID, activeOperationID,
onSetActiveOperationID, activeVersionID, myself, collaborators} = this.props,
onSetActiveOperationID, activeVersionID, myself, collaborators, transitioning} = this.props,
metadata = conflictDescriptor.metadata[operationID],
hasConflicts = !!conflictDescriptor.conflicts[versionID][operationID],
conflictKeys = hasConflicts && Object.keys(conflictDescriptor.conflicts[versionID][operationID]),
Expand All @@ -60,7 +61,7 @@ export default class extends React.Component<Props, State> {
(operationID === activeOperationID ||
(hasConflicts && conflictKeys.includes(activeOperationID)))
? 'highlightRed'
: hasConflicts && conflictEntries.length > 0
: !transitioning && hasConflicts && conflictEntries.length > 0
? 'highlightDarkRed'
: undefined}
comments={hasConflicts && conflictEntries.length > 0
Expand Down
14 changes: 10 additions & 4 deletions client/src/components/conflictView/Version.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ interface Props {
collaboratorsInFavor: Collaborator[],
settings: Settings,
onVote: (versionID: string) => () => void,
allowedToVote: boolean
allowedToVote: boolean,
transitioning: boolean,
transitionResolutionOutcome?: string
};

interface State {
Expand All @@ -45,13 +47,16 @@ export default class extends React.Component<Props, State> {
render() {
const {ownVotedVersionID, conflictDescriptor, versionID, versionIndex, activeOperationID,
onSetActiveOperationID, activeVersionID, onSetActiveVersionID, myself, collaborators,
collaboratorsInFavor, settings, onVote, allowedToVote} = this.props,
collaboratorsInFavor, settings, onVote, allowedToVote, transitioning,
transitionResolutionOutcome} = this.props,
operationIDs = conflictDescriptor.versions[versionID],
ButtonComponent = ownVotedVersionID === versionID ? PrimaryButton : DefaultButton;

return versionID !== 'neutral' &&
<div className="version">
<div>
<div className={transitioning
? (transitionResolutionOutcome === versionID ? 'outcome' : 'discarded')
: undefined}>
<div className="header">
<div className="heading">
{i18n.t('conflictResolution.version')} {String.fromCharCode(65 + versionIndex)}
Expand All @@ -72,7 +77,8 @@ export default class extends React.Component<Props, State> {
onSetActiveOperationID={onSetActiveOperationID}
activeVersionID={activeVersionID}
myself={myself}
collaborators={collaborators}/>)}
collaborators={collaborators}
transitioning={transitioning}/>)}
<div className="footer">
<ButtonComponent
onClick={onVote(versionID)}
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/constraintsView/ConstraintsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import FeatureModel, {Constraint} from '../../modeling/FeatureModel';
import {DetailsList, IColumn, SelectionMode} from 'office-ui-fabric-react/lib/DetailsList';
import i18n from '../../i18n';
import ConstraintView from './ConstraintView';
import {KernelConflictDescriptor} from '../../modeling/types';

export function enableConstraintsView(featureModel?: FeatureModel): boolean {
return featureModel ? featureModel.constraints.length > 0 : false;
export function enableConstraintsView(featureModel?: FeatureModel, transitionConflictDescriptor?: KernelConflictDescriptor): boolean {
return featureModel && !transitionConflictDescriptor ? featureModel.constraints.length > 0 : false;
}

interface Props {
Expand Down
8 changes: 5 additions & 3 deletions client/src/components/overlays/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import {enableConstraintsView} from '../constraintsView/ConstraintsView';
import {defaultSettings, Settings} from '../../store/settings';
import {preconditions} from '../../modeling/preconditions';
import {redirectToArtifactPath} from '../../router';
import {KernelConflictDescriptor} from '../../modeling/types';

interface Props {
artifactPaths: ArtifactPath[],
collaborativeSessions: CollaborativeSession[],
isOpen: boolean,
featureDiagramLayout?: FeatureDiagramLayoutType,
featureModel?: FeatureModel,
transitionConflictDescriptor?: KernelConflictDescriptor,
settings: Settings,
onDismiss: () => void,
onShowOverlay: OnShowOverlayFunction,
Expand Down Expand Up @@ -320,14 +322,14 @@ export default class extends React.Component<Props, State> {
}, {
key: 'toggleConstraintView',
text: i18n.t('commandPalette.featureDiagram.toggleConstraintView'),
disabled: () => !enableConstraintsView(this.props.featureModel),
disabled: () => !enableConstraintsView(this.props.featureModel, this.props.transitionConflictDescriptor),
action: this.action(() => this.props.onSetSetting(
{path: 'views.splitAt', value: (splitAt: number) =>
splitAt > defaultSettings.views.splitAt ? defaultSettings.views.splitAt : 1}))
}, {
key: 'toggleConstraintViewSplitDirection',
text: i18n.t('commandPalette.featureDiagram.toggleConstraintViewSplitDirection'),
disabled: () => !enableConstraintsView(this.props.featureModel),
disabled: () => !enableConstraintsView(this.props.featureModel, this.props.transitionConflictDescriptor),
action: this.action(() => this.props.onSetSetting(
{path: 'views.splitDirection', value: (splitDirection: 'horizontal' | 'vertical') =>
splitDirection === 'horizontal' ? 'vertical' : 'horizontal'}))
Expand Down Expand Up @@ -516,7 +518,7 @@ export default class extends React.Component<Props, State> {
}, {
text: i18n.t('commandPalette.featureDiagram.constraint.set'),
icon: 'Edit',
disabled: () => !enableConstraintsView(this.props.featureModel),
disabled: () => !enableConstraintsView(this.props.featureModel, this.props.transitionConflictDescriptor),
action: this.actionWithArguments(
[{
title: i18n.t('commandPalette.oldConstraint'),
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/overlays/OverlayContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const OverlayContainer = (props: StateDerivedProps & RouteProps) => (
isOpen={props.overlay === OverlayType.commandPalette}
featureDiagramLayout={props.featureDiagramLayout}
featureModel={props.featureModel}
transitionConflictDescriptor={props.transitionConflictDescriptor}
settings={props.settings!}
onDismiss={() => props.onHideOverlay!({overlay: OverlayType.commandPalette})}
onShowOverlay={props.onShowOverlay!}
Expand Down Expand Up @@ -222,7 +223,8 @@ export default withRouter(connect(
featureDiagramLayout: collaborativeSession.layout,
isSelectMultipleFeatures: collaborativeSession.isSelectMultipleFeatures,
selectedFeatureIDs: collaborativeSession.selectedFeatureIDs,
featureModel: getCurrentFeatureModel(state)
featureModel: getCurrentFeatureModel(state),
transitionConflictDescriptor: collaborativeSession.transitionConflictDescriptor
};
}),
(dispatch): StateDerivedProps => ({
Expand Down
4 changes: 4 additions & 0 deletions client/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const constants = {
minWidth: 500,
minHeight: 500,
maxCollapsibleNodes: (nodes: any[]) => nodes.length > 100 ? nodes.length / 10 : 1
},
conflictView: {
transition: 1000,
transitionNeutral: 600
}
},
constraint: {
Expand Down
3 changes: 2 additions & 1 deletion client/src/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ const actions = {
overlay: {
show: createStandardAction('ui/overlay/show')<{overlay: OverlayType, overlayProps: OverlayProps, selectOneFeature?: string}>(),
hide: createStandardAction('ui/overlay/hide')<{overlay: OverlayType}>()
}
},
endConflictViewTransition: createStandardAction('ui/endConflictViewTransition')<void>()
},
server: {
receive: createStandardAction('server/receiveMessage')<Message>(),
Expand Down
16 changes: 14 additions & 2 deletions client/src/store/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,9 @@ function serverReceiveReducer(state: State, action: Action): State {
kernelContext,
kernelCombinedEffect,
voterSiteIDs: undefined,
votes: {}
votes: {},
transitionResolutionOutcome: action.payload.versionID,
transitionConflictDescriptor: (<FeatureDiagramCollaborativeSession>collaborativeSession).kernelCombinedEffect
};
}));
if (isEditingFeatureModel(state))
Expand Down Expand Up @@ -413,7 +415,7 @@ function uiReducer(state: State, action: Action): State {
(<FeatureDiagramCollaborativeSession>collaborativeSession).collapsedFeatureIDs,
getFeatureIDsBelowWithActualChildren(state, currentArtifactPath!, action.payload.featureIDs))
})))
: state;
: state;

case getType(actions.ui.overlay.show):
state = updateOverlay(state, action.payload.overlay, action.payload.overlayProps);
Expand All @@ -427,6 +429,16 @@ function uiReducer(state: State, action: Action): State {
return state.overlay === action.payload.overlay
? updateOverlay(state, OverlayType.none, {})
: state;

case getType(actions.ui.endConflictViewTransition):
return isEditingFeatureModel(state)
? getNewState(state, 'collaborativeSessions',
getNewCollaborativeSessions(state, currentArtifactPath!, (collaborativeSession: CollaborativeSession) => ({
...collaborativeSession,
transitionResolutionOutcome: undefined,
transitionConflictDescriptor: undefined
})))
: state;
}

return state;
Expand Down
Loading

0 comments on commit 5f7f755

Please sign in to comment.