Skip to content

Commit

Permalink
Basic feature model import
Browse files Browse the repository at this point in the history
  • Loading branch information
ekuiter committed Mar 23, 2019
1 parent ee4c63d commit 6eff2a9
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 20 deletions.
24 changes: 12 additions & 12 deletions client/src/components/CommandBarContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import {enableConstraintsView} from './constraintsView/ConstraintsView';

const CommandBarContainer = (props: StateDerivedProps) => (
<CommandBar
items={[
...props.featureModel
? [{
key: 'file',
text: i18n.t('commands.file'),
subMenuProps: {
items: [
commands.featureDiagram.export(props.featureDiagramLayout!, props.onShowOverlay!)
]
}
}]
: [],
items={[{
key: 'file',
text: i18n.t('commands.file'),
subMenuProps: {
items: [
commands.featureDiagram.addArtifact(props.onShowOverlay!),
...props.featureModel
? [commands.featureDiagram.export(props.featureDiagramLayout!, props.onShowOverlay!)]
: []
]
}
},
...props.featureModel
? [{
key: 'edit',
Expand Down
10 changes: 8 additions & 2 deletions client/src/components/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import FeatureModel from '../modeling/FeatureModel';
import {Feature} from '../modeling/types';
import {defaultSettings} from '../store/settings';
import {preconditions} from '../modeling/preconditions';
import logger from 'src/helpers/logger';
import logger from '../helpers/logger';

const exportFormatItem = (featureDiagramLayout: FeatureDiagramLayoutType,
onShowOverlay: OnShowOverlayFunction, format: FormatType) =>
Expand Down Expand Up @@ -77,10 +77,16 @@ const commands = {
onClick: onRedo
}),
featureDiagram: {
addArtifact: (onShowOverlay: OnShowOverlayFunction) => ({
key: 'addArtifact',
text: i18n.t('commands.addArtifact'),
iconProps: {iconName: 'CloudUpload'},
onClick: () => onShowOverlay({overlay: OverlayType.addArtifactDialog, overlayProps: {}})
}),
export: (featureDiagramLayout: FeatureDiagramLayoutType, onShowOverlay: OnShowOverlayFunction) => ({
key: 'export',
text: i18n.t('commands.featureDiagram.export'),
iconProps: {iconName: 'Share'},
iconProps: {iconName: 'CloudDownload'},
subMenuProps: {
items: [
...exportFormatItem(featureDiagramLayout, onShowOverlay, FormatType.png),
Expand Down
117 changes: 117 additions & 0 deletions client/src/components/overlays/AddArtifactDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* A Fabric dialog for renaming a feature.
*/

import React, {ChangeEvent} from 'react';
import i18n from '../../i18n';
import deferred from '../../helpers/deferred';
import {ITextField, TextField} from 'office-ui-fabric-react/lib/TextField';
import logger from '../../helpers/logger';
import {Dropdown, IDropdownOption} from 'office-ui-fabric-react/lib/Dropdown';
import Dialog, {DialogFooter} from 'office-ui-fabric-react/lib/Dialog';
import {PrimaryButton} from 'office-ui-fabric-react/lib/Button';
import {OnAddArtifactFunction} from '../../store/types';
import {ComboBox, IComboBoxOption} from 'office-ui-fabric-react/lib/ComboBox';
import {ArtifactPath} from '../../types';
import {arrayUnique} from '../../helpers/array';

interface AddArtifactDialogProps {
isOpen: boolean,
onDismiss: () => void,
onAddArtifact: OnAddArtifactFunction,
artifactPaths: ArtifactPath[]
};

interface AddArtifactDialogState {
project?: string,
artifact?: string,
encoding?: string,
file?: File
};

const defaultEncoding = 'UTF-8';

export default class extends React.Component<AddArtifactDialogProps, AddArtifactDialogState> {
state: AddArtifactDialogState = {};
artifactRef = React.createRef<ITextField>();

onProjectChange = (_event: any, option: IDropdownOption) => this.setState({project: option ? option.text : undefined});
onArtifactChange = (_event: any, artifact?: string) => this.setState({artifact});
onEncodingChange = (_event: any, option: IComboBoxOption, index: number, value: string) =>
this.setState({encoding: value || (option ? option.text : undefined)});

onSourceChange = (event: ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length === 1)
this.setState({file: files[0]});
else if (files && files.length > 1)
logger.warn(() => 'only one artifact can be added at a time');
}

onLayerDidMount = deferred(() => {
this.artifactRef.current!.focus();
this.artifactRef.current!.select();
});

onSubmit = () => {
let {project, artifact, encoding, file} = this.state;
project = project && project.trim();
artifact = artifact && artifact.trim();
encoding = encoding && encoding.trim();

if (!project || !artifact) {
logger.warn(() => 'no artifact path supplied'); // TODO: better error UI
return;
}
if (!file) {
logger.warn(() => 'no file supplied'); // TODO: better error UI
return;
}

var reader = new FileReader();
reader.onload = (e: any) => {
const source = e.target.result;
this.props.onAddArtifact({artifactPath: {project: project!, artifact: artifact!}, source});
this.setState({project: undefined, artifact: undefined, encoding: undefined, file: undefined});
this.props.onDismiss();
};
reader.readAsText(file, encoding || defaultEncoding);
};

render() {
const {isOpen, onDismiss} = this.props,
projectOptions = arrayUnique(this.props.artifactPaths.map(artifactPath => artifactPath.project))
.map(project => ({key: project, text: project}));

return (
<Dialog
hidden={!isOpen}
onDismiss={onDismiss}
modalProps={{onLayerDidMount: this.onLayerDidMount}}
dialogContentProps={{title: i18n.t('overlays.addArtifactDialog.title')}}>
<Dropdown
label={i18n.t('commandPalette.project')}
onChange={this.onProjectChange}
options={projectOptions}/>
<TextField
componentRef={this.artifactRef}
label={i18n.t('commandPalette.artifact')}
value={this.state.artifact}
onChange={this.onArtifactChange}/>
<ComboBox
text={this.state.encoding || defaultEncoding}
label={i18n.t('overlays.addArtifactDialog.encoding')}
allowFreeform
autoComplete="on"
options={['UTF-8', 'ISO-8859-1', 'Windows-1252']
.map(encoding => ({key: encoding, text: encoding}))}
onChange={this.onEncodingChange}/>
{i18n.getElement('overlays.addArtifactDialog.formatNotice')}
<input type="file" onChange={this.onSourceChange}/>
<DialogFooter>
<PrimaryButton onClick={this.onSubmit} text={i18n.t('overlays.addArtifactDialog.upload')}/>
</DialogFooter>
</Dialog>
);
}
}
9 changes: 7 additions & 2 deletions client/src/components/overlays/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import logger from '../../helpers/logger';
import {Persistor} from 'redux-persist';
import {enableConstraintsView} from '../constraintsView/ConstraintsView';
import {defaultSettings, Settings} from '../../store/settings';
import {preconditions} from 'src/modeling/preconditions';
import {preconditions} from '../../modeling/preconditions';

interface Props {
artifactPaths: ArtifactPath[],
Expand Down Expand Up @@ -243,8 +243,13 @@ export default class extends React.Component<Props, State> {
shortcut: getShortcutText('redo'),
action: this.action(this.props.onRedo)
},*/ {
text: i18n.t('commandPalette.addArtifact'),
icon: 'CloudUpload',
action: this.action(() =>
this.props.onShowOverlay({overlay: OverlayType.addArtifactDialog, overlayProps: {}}))
}, {
text: i18n.t('commandPalette.featureDiagram.export'),
icon: 'Share',
icon: 'CloudDownload',
disabled: () => !this.props.featureModel,
action: this.actionWithArguments(
[{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class extends FeatureComponent()<Props> {
isOpen={this.props.isOpen}
onDismiss={this.props.onDismiss}
title={i18n.t('overlays.featureSetDescriptionDialog.title')}
submitText={i18n.t('overlays.featureSetDescriptionDialog.rename')}
submitText={i18n.t('overlays.featureSetDescriptionDialog.save')}
defaultValue={feature.description}
onSubmit={description => {
if (preconditions.featureDiagram.feature.setDescription(feature.ID, this.props.featureModel))
Expand Down
11 changes: 11 additions & 0 deletions client/src/components/overlays/OverlayContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {OverlayType} from '../../types';
import {State, StateDerivedProps} from '../../store/types';
import logger from '../../helpers/logger';
import CommandPalette from './CommandPalette';
import AddArtifactDialog from './AddArtifactDialog';

const OverlayContainer = (props: StateDerivedProps) => (
<React.Fragment>
Expand Down Expand Up @@ -118,6 +119,14 @@ const OverlayContainer = (props: StateDerivedProps) => (
onSetFeatureDescription={props.onSetFeatureDescription!}
{...props.overlayProps}/>}

{props.overlay === OverlayType.addArtifactDialog &&
<AddArtifactDialog
isOpen={true}
onDismiss={() => props.onHideOverlay!({overlay: OverlayType.addArtifactDialog})}
onAddArtifact={props.onAddArtifact!}
artifactPaths={props.artifactPaths!}
{...props.overlayProps}/>}

{props.overlay === OverlayType.exportDialog &&
<ExportDialog
isOpen={true}
Expand Down Expand Up @@ -197,6 +206,8 @@ export default connect(
(dispatch): StateDerivedProps => ({
onFitToScreen: () => dispatch(actions.ui.featureDiagram.fitToScreen()),
onSetFeatureDiagramLayout: payload => dispatch(actions.ui.featureDiagram.setLayout(payload)),
onAddArtifact: payload => dispatch<any>(actions.server.addArtifact(payload)),
onRemoveArtifact: payload => dispatch<any>(actions.server.removeArtifact(payload)),
onJoinRequest: payload => dispatch<any>(actions.server.joinRequest(payload)),
onLeaveRequest: payload => dispatch<any>(actions.server.leaveRequest(payload)),
onUndo: () => dispatch<any>(actions.server.undo({})),
Expand Down
2 changes: 1 addition & 1 deletion client/src/helpers/FontComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class extends React.Component<Props, State> {
return (
<ComboBox
text={this.state.errorMessage ? undefined : selectedFont}
allowFreeform={true}
allowFreeform
autoComplete="on"
options={options}
onRenderOption={(props?: {text: string}) =>
Expand Down
2 changes: 1 addition & 1 deletion client/src/helpers/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function setAdd<T>(set: T[], elementOrElements: T | T[],
let elements: T[];
elements = Array.isArray(elementOrElements) ? elementOrElements : [elementOrElements];
elements = arrayUnique(elements, keyFn, eqFn);
return setRemove(set, elements, keyFn).concat(elements);
return setRemove(set, elements, keyFn, eqFn).concat(elements);
}

/**
Expand Down
15 changes: 14 additions & 1 deletion client/src/i18n.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const translationMap = {
about: 'About…',
undo: 'Undo',
redo: 'Redo',
addArtifact: 'Import…',
featureDiagram: {
export: 'Export as',
svg: 'SVG…',
Expand Down Expand Up @@ -130,6 +131,7 @@ const translationMap = {
leaveRequest: 'Leave collaborative session',
settings: 'Settings',
about: 'About',
addArtifact: 'Import feature model',
featureDiagram: {
export: 'Export feature model',
svg: 'SVG',
Expand Down Expand Up @@ -259,7 +261,18 @@ const translationMap = {
},
featureSetDescriptionDialog: {
title: 'Set feature description',
rename: 'Save'
save: 'Save'
},
addArtifactDialog: {
title: 'Import feature model',
upload: 'Upload',
encoding: 'File encoding',
formatNotice: (
<p>
You can import any feature model that is compatible with <Link
href="https://featureide.github.io/" target="_blank">FeatureIDE</Link>.
</p>
),
},
exportDialog: {
export: 'Export',
Expand Down
4 changes: 4 additions & 0 deletions client/src/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ const actions = {
},
server: {
receive: createStandardAction('server/receiveMessage')<Message>(),
addArtifact: createMessageAction(({artifactPath, source}: {artifactPath: ArtifactPath, source: string}) =>
({type: MessageType.ADD_ARTIFACT, artifactPath, source})),
removeArtifact: createMessageAction(({artifactPath}: {artifactPath: ArtifactPath}) =>
({type: MessageType.REMOVE_ARTIFACT, artifactPath})),
joinRequest: createMessageAction(({artifactPath}: {artifactPath: ArtifactPath}) => ({type: MessageType.JOIN_REQUEST, artifactPath})),
leaveRequest: createMessageAction(({artifactPath}: {artifactPath: ArtifactPath}) => ({type: MessageType.LEAVE_REQUEST, artifactPath})),
undo: createMessageAction(() => ({type: MessageType.ERROR})), // TODO
Expand Down
4 changes: 4 additions & 0 deletions client/src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export type OnSetSettingFunction = (payload: {path: string, value: any}) => void
export type OnResetSettingsFunction = () => void;
export type OnSetCurrentArtifactPathFunction = (payload: {artifactPath?: ArtifactPath}) => void;

export type OnAddArtifactFunction = (payload: {artifactPath: ArtifactPath, source: string}) => Promise<void>;
export type OnRemoveArtifactFunction = (payload: {artifactPath: ArtifactPath}) => Promise<void>;
export type OnJoinRequestFunction = (payload: {artifactPath: ArtifactPath}) => Promise<void>;
export type OnLeaveRequestFunction = (payload: {artifactPath: ArtifactPath}) => Promise<void>;
export type OnUndoFunction = () => Promise<void>;
Expand Down Expand Up @@ -135,6 +137,8 @@ export type StateDerivedProps = Partial<{
onResetSettings: OnResetSettingsFunction,
onSetCurrentArtifactPath: OnSetCurrentArtifactPathFunction,

onAddArtifact: OnAddArtifactFunction,
onRemoveArtifact: OnRemoveArtifactFunction,
onJoinRequest: OnJoinRequestFunction,
onLeaveRequest: OnLeaveRequestFunction,
onUndo: OnUndoFunction,
Expand Down
1 change: 1 addition & 0 deletions client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum OverlayType {
featurePanel = 'featurePanel',
featureRenameDialog = 'featureRenameDialog',
featureSetDescriptionDialog = 'featureSetDescriptionDialog',
addArtifactDialog = 'addArtifact',
exportDialog = 'export',
featureCallout = 'featureCallout',
featureContextualMenu = 'featureContextualMenu'
Expand Down

0 comments on commit 6eff2a9

Please sign in to comment.