Skip to content

Commit

Permalink
Added diagram open errors dialog.
Browse files Browse the repository at this point in the history
  • Loading branch information
anickle060193 committed Mar 3, 2018
1 parent 3509bf7 commit fdf320c
Show file tree
Hide file tree
Showing 14 changed files with 3,068 additions and 16 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ShortcutManager from 'components/ShortcutManager';
import Layout from 'components/Layout';
import UpdateNotification from 'components/UpdateNotification';
import SettingsDialog from 'components/SettingsDialog';
import DiagramOpenErrorsDialog from 'components/DiagramOpenErrorsDialog';
import store from 'store';
import { createAutoUpdater } from 'utils/auto_updater';
import { createApplicationMenu } from 'utils/menu';
Expand All @@ -26,6 +27,7 @@ export default class App extends React.Component
<Layout />
<UpdateNotification />
<SettingsDialog />
<DiagramOpenErrorsDialog />
</ShortcutManager>
</Provider>
);
Expand Down
72 changes: 72 additions & 0 deletions src/components/DiagramOpenErrorsDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as React from 'react';
import { connect } from 'react-redux';

import Dialog from 'components/Dialog';
import { clearDiagramOpenErrors } from 'store/reducers/editor';

import './styles.css';

interface PropsFromState
{
diagramOpenErrors: string[] | null;
}

interface PropsFromDispatch
{
clearDiagramOpenErrors: typeof clearDiagramOpenErrors;
}

type Props = PropsFromState & PropsFromDispatch;

class DiagramOpenErrorsDialog extends React.Component<Props>
{
render()
{
return (
<Dialog
show={!!this.props.diagramOpenErrors && this.props.diagramOpenErrors.length > 0}
onClose={this.onClose}
closeOnShadeClick={false}
style={{
backgroundColor: 'rgb(228, 95, 95)',
width: '80%',
margin: '1rem',
color: 'white'
}}
>
<div className="open-errors-dialog">
<h1 className="text-center pb-1">Diagram Open Errors</h1>
<ul>
{this.props.diagramOpenErrors && this.props.diagramOpenErrors.map( ( error, i ) => (
<li key={i}>
{error}
</li>
) )}
</ul>

<button
className="btn btn-outline-light ml-auto"
type="button"
onClick={this.onClose}
>
Close
</button>
</div>
</Dialog>
);
}

private onClose = () =>
{
this.props.clearDiagramOpenErrors();
}
}

export default connect<PropsFromState, PropsFromDispatch, {}, RootState>(
( state ) => ( {
diagramOpenErrors: state.editor.diagramOpenErrors
} ),
{
clearDiagramOpenErrors
}
)( DiagramOpenErrorsDialog );
16 changes: 16 additions & 0 deletions src/components/DiagramOpenErrorsDialog/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.open-errors-dialog
{
margin: 1rem;
display: flex;
flex-direction: column;
}

.open-errors-dialog h1
{
border-bottom: 1px solid white;
}

.open-errors-dialog li
{
user-select: text;
}
12 changes: 8 additions & 4 deletions src/components/Dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import './styles.css';
interface Props
{
show: boolean;
closeOnShadeClick: boolean;
onClose: () => void;

style?: React.CSSProperties;
}

interface State
Expand Down Expand Up @@ -48,20 +51,21 @@ export default class Dialog extends React.Component<Props, State>
'dialog-shade',
'dialog-' + this.state.state
].join( ' ' )}
onClick={this.onClose}
onClick={this.onShadeClick}
onAnimationEnd={this.onAnimationEnd}
>
<div className="dialog">
<div className="dialog" style={this.props.style}>
{this.props.children}
</div>
</div>,
document.body
);
}

private onClose = ( e: React.MouseEvent<HTMLElement> ) =>
private onShadeClick = ( e: React.MouseEvent<HTMLElement> ) =>
{
if( e.target === e.currentTarget )
if( this.props.closeOnShadeClick
&& e.target === e.currentTarget )
{
this.props.onClose();
}
Expand Down
2 changes: 0 additions & 2 deletions src/components/Dialog/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@
margin: 2rem;
background: white;
border-radius: 4px;
min-width: 80%;
min-height: 20%;
box-shadow: 1px 1px 5px 0px rgba(77, 77, 77, 0.664);
}

Expand Down
1 change: 1 addition & 0 deletions src/components/SettingsDialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class SettingsDialog extends React.Component<Props, State>
<Dialog
show={this.props.show}
onClose={this.onClose}
closeOnShadeClick={true}
>
<div className="settings">
<div className="container-fluid">
Expand Down
3 changes: 2 additions & 1 deletion src/store/reducers/drawings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ const baseReducer = reducerWithInitialState( initialState )
{
return {
...state,
drawings
drawings,
selectedDrawingId: null
};
} );

Expand Down
16 changes: 15 additions & 1 deletion src/store/reducers/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ export interface State
originX: number;
originY: number;
selectedDrawingId: string | null;
diagramOpenErrors: string[] | null;
}

const initialState: State = {
tool: Tool.Cursor,
scaleLevel: DEFAULT_SCALE_LEVEL,
originX: 0.0,
originY: 0.0,
selectedDrawingId: null
selectedDrawingId: null,
diagramOpenErrors: null
};

const actionCreator = actionCreatorFactory();
Expand All @@ -36,6 +38,8 @@ export const decrementScaleLevel = actionCreator( 'DECREMENT_SCALE_LEVEL' );
export const resetScaleLevel = actionCreator( 'RESET_SCALE_LEVEL' );
export const setOrigin = actionCreator<{ originX: number, originY: number }>( 'SET_ORIGIN' );
export const resetOrigin = actionCreator( 'RESET_ORIGIN' );
export const setDiagramOpenErrors = actionCreator<string[]>( 'SET_DIAGRAM_OPEN_ERRORS' );
export const clearDiagramOpenErrors = actionCreator( 'CLEAR_DIAGRAM_OPEN_ERRORS' );

export const reducer = reducerWithInitialState( initialState )
.case( setTool, ( state, tool ) =>
Expand Down Expand Up @@ -69,4 +73,14 @@ export const reducer = reducerWithInitialState( initialState )
...state,
originX: 0.0,
originY: 0.0
} ) )
.case( setDiagramOpenErrors, ( state, diagramOpenErrors ) =>
( {
...state,
diagramOpenErrors
} ) )
.case( clearDiagramOpenErrors, ( state ) =>
( {
...state,
diagramOpenErrors: null
} ) );
87 changes: 83 additions & 4 deletions src/utils/drawing_validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import DefineKeywords from 'ajv-keywords';

const defineKeywords = require( 'ajv-keywords' ) as typeof DefineKeywords;

import { Drawing, DrawingType, dashStyles, HorizontalAlign, VerticalAlign } from 'utils/draw';
import { Drawing, DrawingType, dashStyles, HorizontalAlign, VerticalAlign, DrawingMap } from 'utils/draw';
import { arrayToMap, mapToArray } from 'utils/utils';

const MIN_COORDINATE = -1000000;
const MAX_COORDINATE = +1000000;
Expand Down Expand Up @@ -124,7 +125,10 @@ export const validator = defineKeywords( new Ajv( {
properties: {
'type': { const: DrawingType.Between },
'height': { type: 'number', minimum: 0, maximum: MAX_COORDINATE }
}
},
allOf: [
{ $ref: Schemas.ContainsGuideLineDrawing }
]
} )
.addSchema( {
$id: Schemas.ConnectedPathLineEndPoint,
Expand Down Expand Up @@ -266,9 +270,11 @@ validator
selectDefault: false
} );

( window as any ).validator = validator; // tslint:disable-line no-any

export interface DrawingsParseResult
{
drawings?: Drawing[];
drawings?: DrawingMap;
errors?: string[];
}

Expand Down Expand Up @@ -298,7 +304,12 @@ export function parseDrawings( drawingsJson: string ): DrawingsParseResult
errors.push( `There was an error with drawing ${i}: ${validator.errorsText()}` );
}
} );
return { drawings, errors };

let drawingMap = arrayToMap( drawings );

validatePathLineDrawings( drawingMap, errors );

return { drawings: drawingMap, errors };
}
catch( e )
{
Expand All @@ -310,3 +321,71 @@ export function parseDrawings( drawingsJson: string ): DrawingsParseResult
errors: [ error ? error.toString() : 'Failed to parse drawings.' ]
};
}

function validatePathLineDrawings( drawings: DrawingMap, errors: string[] )
{
let removedIds: string[] = [];

for( let drawing of mapToArray( drawings ) )
{
if( drawing.type === DrawingType.PathLine )
{
if( drawing.start.connected )
{
let anchor = drawings[ drawing.start.anchorId ];
if( !anchor )
{
removedIds.push( drawing.id );
errors.push( `Drawing {${drawing.id}} removed as start-point connected anchor drawing (${drawing.start.anchorId}) does not exist.` );
}
else if( anchor.id === drawing.id )
{
removedIds.push( drawing.id );
errors.push( `Drawing {${drawing.id}} removed due to circular reference.` );
}
}

if( drawing.end.connected )
{
let anchor = drawings[ drawing.end.anchorId ];
if( !anchor )
{
removedIds.push( drawing.id );
errors.push( `Drawing {${drawing.id}} removed as end-point connected anchor drawing (${drawing.end.anchorId}) does not exist.` );
}
else if( anchor.id === drawing.id )
{
removedIds.push( drawing.id );
errors.push( `Drawing {${drawing.id}} removed due to circular reference.` );
}
}
}
}

let firstRemovalPass = true;
while( removedIds.length > 0 )
{
let removedId = removedIds.pop()!;

delete drawings[ removedId ];

if( !firstRemovalPass )
{
errors.push( `Drawing {${removedId}} removed due to removal of dependent drawing.` );
}

for( let drawing of mapToArray( drawings ) )
{
if( drawing.type === DrawingType.PathLine )
{
if( drawing.start.connected && drawing.start.anchorId === removedId
|| drawing.end.connected && drawing.end.anchorId === removedId )
{
removedIds.push( drawing.id );
}
}
}

firstRemovalPass = false;
}
}
9 changes: 6 additions & 3 deletions src/utils/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ import { MenuItemConstructorOptions } from 'electron';
import { Store } from 'redux';

import { setDrawings } from 'store/reducers/drawings';
import { setDiagramOpenErrors } from 'store/reducers/editor';
import electron, { openDiagram, saveDiagram, exportImage } from 'utils/electron';
import { arrayToMap } from 'utils/utils';

async function onOpenDiagram( store: Store<RootState> )
{
let result = await openDiagram();
if( result && result.drawings )
{
let drawings = arrayToMap( result.drawings );
store.dispatch( setDrawings( drawings ) );
store.dispatch( setDrawings( result.drawings ) );
}
if( result && result.errors )
{
store.dispatch( setDiagramOpenErrors( result.errors ) );
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export function mapToArray<T>( m: { [ key: string ]: T } )
{
return Object.keys( m ).map( ( key ) => m[ key ] );
return Object.values( m );
}

export function arrayToMap<T extends { id: string }>( arr: T[] )
Expand Down
Loading

0 comments on commit fdf320c

Please sign in to comment.