From dc866b552d5a5538661aa71d430b57fec9942fc9 Mon Sep 17 00:00:00 2001 From: FMS-Cat Date: Fri, 24 Apr 2020 02:03:05 +0900 Subject: [PATCH] (from automaton-with-gui repo) ChannelList/CurveList: create new button --- src/AutomatonWithGUI.tsx | 2 +- .../components/AutomatonStateListener.tsx | 8 ++ src/view/components/ChannelList.tsx | 84 ++++++++++++++++++- src/view/components/ChannelListEntry.tsx | 1 - src/view/components/CurveList.tsx | 80 ++++++++++++++++-- src/view/components/CurveListEntry.tsx | 2 - src/view/icons/Icons.ts | 1 + src/view/icons/plus.svg | 7 ++ src/view/states/Automaton.ts | 5 ++ src/view/states/CurveEditor.ts | 4 + 10 files changed, 181 insertions(+), 13 deletions(-) create mode 100644 src/view/icons/plus.svg diff --git a/src/AutomatonWithGUI.tsx b/src/AutomatonWithGUI.tsx index a11c637f..9bcd5901 100644 --- a/src/AutomatonWithGUI.tsx +++ b/src/AutomatonWithGUI.tsx @@ -414,7 +414,7 @@ export class AutomatonWithGUI extends Automaton * @param index Index of the curve */ public removeCurve( index: number ): void { - delete this.__curves[ index ]; + this.__curves.splice( index, 1 ); this.__emit( 'removeCurve', { index } ); diff --git a/src/view/components/AutomatonStateListener.tsx b/src/view/components/AutomatonStateListener.tsx index dd016c16..83a52b60 100644 --- a/src/view/components/AutomatonStateListener.tsx +++ b/src/view/components/AutomatonStateListener.tsx @@ -369,6 +369,13 @@ const AutomatonStateListener = ( props: AutomatonStateListenerProps ): JSX.Eleme createCurve( event.index, event.curve ); } ); + const handleRemoveCurve = automaton.on( 'removeCurve', ( event ) => { + dispatch( { + type: 'Automaton/RemoveCurve', + curve: event.index + } ); + } ); + const handleChangeShouldSave = automaton.on( 'changeShouldSave', ( event ) => { dispatch( { type: 'Automaton/SetShouldSave', @@ -387,6 +394,7 @@ const AutomatonStateListener = ( props: AutomatonStateListenerProps ): JSX.Eleme automaton.off( 'createChannel', handleCreateChannel ); automaton.off( 'removeChannel', handleRemoveChannel ); automaton.off( 'createCurve', handleCreateCurve ); + automaton.off( 'removeCurve', handleRemoveCurve ); automaton.off( 'changeShouldSave', handleChangeShouldSave ); }; }, diff --git a/src/view/components/ChannelList.tsx b/src/view/components/ChannelList.tsx index 6000939a..b1138174 100644 --- a/src/view/components/ChannelList.tsx +++ b/src/view/components/ChannelList.tsx @@ -1,12 +1,35 @@ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { useDispatch, useSelector } from '../states/store'; import { ChannelListEntry } from './ChannelListEntry'; +import { Colors } from '../constants/Colors'; +import { Icons } from '../icons/Icons'; import styled from 'styled-components'; -import { useSelector } from '../states/store'; // == styles ======================================================================================= +const NewChannelIcon = styled.img` + fill: ${ Colors.gray }; + height: 16px; +`; + +const NewChannelButton = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 20px; + margin: 2px 0; + cursor: pointer; + background: ${ Colors.back3 }; + + &:active { + background: ${ Colors.back4 }; + } +`; + const StyledChannelListEntry = styled( ChannelListEntry )` width: 100%; - margin: 0.125rem 0; + height: 20px; + margin: 2px 0; cursor: pointer; `; @@ -19,10 +42,57 @@ export interface ChannelListProps { } const ChannelList = ( { className }: ChannelListProps ): JSX.Element => { - const { channelNames } = useSelector( ( state ) => ( { + const dispatch = useDispatch(); + const { + automaton, + channelNames + } = useSelector( ( state ) => ( { + automaton: state.automaton.instance, channelNames: state.automaton.channelNames } ) ); + const handleClickNewChannel = useCallback( + ( event: React.MouseEvent ) => { + if ( !automaton ) { return; } + + dispatch( { + type: 'TextPrompt/Open', + position: { x: event.clientX, y: event.clientY }, + placeholder: 'Name for the new channel', + checkValid: ( name ) => { + if ( name === '' ) { return false; } + if ( automaton.getChannel( name ) != null ) { return false; } + return true; + }, + callback: ( name ) => { + const redo = (): void => { + automaton.createChannel( name ); + + dispatch( { + type: 'Timeline/SelectChannel', + channel: name + } ); + }; + + const undo = (): void => { + automaton.removeChannel( name ); + }; + + dispatch( { + type: 'History/Push', + entry: { + description: `Create Channel: ${ name }`, + redo, + undo + } + } ); + redo(); + } + } ); + }, + [ automaton ] + ); + const sortedChannelNames = useMemo( () => Array.from( channelNames ).sort(), [ channelNames ] @@ -36,6 +106,12 @@ const ChannelList = ( { className }: ChannelListProps ): JSX.Element => { name={ channel } /> ) ) } + + + ); }; diff --git a/src/view/components/ChannelListEntry.tsx b/src/view/components/ChannelListEntry.tsx index cf77812c..f332dc2a 100644 --- a/src/view/components/ChannelListEntry.tsx +++ b/src/view/components/ChannelListEntry.tsx @@ -48,7 +48,6 @@ const Icon = styled.img` const Root = styled.div<{ isSelected: boolean }>` position: relative; - height: 1.25rem; background: ${ ( { isSelected } ) => ( isSelected ? Colors.back4 : Colors.back3 ) }; `; diff --git a/src/view/components/CurveList.tsx b/src/view/components/CurveList.tsx index 9e336f80..fef9fb15 100644 --- a/src/view/components/CurveList.tsx +++ b/src/view/components/CurveList.tsx @@ -1,14 +1,38 @@ +import React, { useCallback } from 'react'; +import { useDispatch, useSelector } from '../states/store'; + import { Colors } from '../constants/Colors'; import { CurveListEntry } from './CurveListEntry'; -import React from 'react'; +import { Icons } from '../icons/Icons'; +import { Metrics } from '../constants/Metrics'; import { Scrollable } from './Scrollable'; import styled from 'styled-components'; -import { useSelector } from '../states/store'; // == styles ======================================================================================= +const NewCurveIcon = styled.img` + fill: ${ Colors.gray }; + height: 16px; +`; + +const NewCurveButton = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: ${ Metrics.curveListEntryHeight }px; + margin: 2px; + cursor: pointer; + background: ${ Colors.back3 }; + + &:active { + background: ${ Colors.back4 }; + } +`; + const StyledCurveListEntry = styled( CurveListEntry )` - width: calc( 100% - 0.25rem ); - margin: 0.125rem; + width: calc( 100% - 4px ); + height: ${ Metrics.curveListEntryHeight }px; + margin: 2px; cursor: pointer; `; @@ -22,10 +46,50 @@ export interface CurveListProps { } const CurveList = ( { className }: CurveListProps ): JSX.Element => { - const { curves } = useSelector( ( state ) => ( { + const dispatch = useDispatch(); + const { automaton, curves } = useSelector( ( state ) => ( { + automaton: state.automaton.instance, curves: state.automaton.curves } ) ); + const handleClickNewCurve = useCallback( + () => { + if ( !automaton ) { return; } + + const curve = automaton.createCurve(); + const index = automaton.getCurveIndex( curve ); + + const redo = (): void => { + const curve = automaton.createCurve(); + const index = automaton.getCurveIndex( curve ); + + dispatch( { + type: 'CurveEditor/SelectCurve', + curve: index + } ); + }; + + const undo = (): void => { + automaton.removeCurve( index ); + }; + + dispatch( { + type: 'History/Push', + entry: { + description: 'Create Curve', + redo, + undo + } + } ); + + dispatch( { + type: 'CurveEditor/SelectCurve', + curve: index + } ); + }, + [ automaton ] + ); + return ( { curves.map( ( curve, iCurve ) => ( @@ -34,6 +98,12 @@ const CurveList = ( { className }: CurveListProps ): JSX.Element => { index={ iCurve } /> ) ) } + + + ); }; diff --git a/src/view/components/CurveListEntry.tsx b/src/view/components/CurveListEntry.tsx index 39611713..9c44c7a1 100644 --- a/src/view/components/CurveListEntry.tsx +++ b/src/view/components/CurveListEntry.tsx @@ -3,7 +3,6 @@ import { useDispatch, useSelector } from '../states/store'; import { Colors } from '../constants/Colors'; import { CurveStatusLevel } from '../../CurveWithGUI'; import { Icons } from '../icons/Icons'; -import { Metrics } from '../constants/Metrics'; import styled from 'styled-components'; // == styles ======================================================================================= @@ -30,7 +29,6 @@ const Icon = styled.img` const Root = styled.div<{ isSelected: boolean }>` position: relative; - height: ${ Metrics.curveListEntryHeight }px; background: ${ ( { isSelected } ) => ( isSelected ? Colors.back4 : Colors.back3 ) }; `; diff --git a/src/view/icons/Icons.ts b/src/view/icons/Icons.ts index 81292bf4..af7d3b13 100644 --- a/src/view/icons/Icons.ts +++ b/src/view/icons/Icons.ts @@ -9,6 +9,7 @@ export const Icons = { Error: require( './error.svg' ).default, Pause: require( './pause.svg' ).default, Play: require( './play.svg' ).default, + Plus: require( './plus.svg' ).default, Redo: require( './redo.svg' ).default, Save: require( './save.svg' ).default, Snap: require( './snap.svg' ).default, diff --git a/src/view/icons/plus.svg b/src/view/icons/plus.svg new file mode 100644 index 00000000..f1c3caf4 --- /dev/null +++ b/src/view/icons/plus.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/view/states/Automaton.ts b/src/view/states/Automaton.ts index 870c1d58..504e3f44 100644 --- a/src/view/states/Automaton.ts +++ b/src/view/states/Automaton.ts @@ -94,6 +94,9 @@ export type Action = { curve: number; length: number; path: string; +} | { + type: 'Automaton/RemoveCurve'; + curve: number; } | { type: 'Automaton/UpdateCurvePath'; curve: number; @@ -202,6 +205,8 @@ export const reducer: Reducer = ( state = initialState, action ) previewTime: null, previewValue: null }; + } else if ( action.type === 'Automaton/RemoveCurve' ) { + newState.curves.splice( action.curve, 1 ); } else if ( action.type === 'Automaton/UpdateCurvePath' ) { newState.curves[ action.curve ].path = action.path; } else if ( action.type === 'Automaton/UpdateCurveStatus' ) { diff --git a/src/view/states/CurveEditor.ts b/src/view/states/CurveEditor.ts index d83c5531..415bb2d2 100644 --- a/src/view/states/CurveEditor.ts +++ b/src/view/states/CurveEditor.ts @@ -146,6 +146,10 @@ export const reducer: Reducer = ( state = initialState, ac if ( length < newState.range.t1 ) { newState.range.t1 = length; } + } else if ( action.type === 'Automaton/RemoveCurve' ) { + if ( state.selectedCurve === action.curve ) { + newState.selectedCurve = null; + } } else if ( action.type === 'Automaton/RemoveCurveNode' ) { newState.selectedItems.nodes = arraySetDiff( newState.selectedItems.nodes, [ action.id ] ); } else if ( action.type === 'Automaton/RemoveCurveFx' ) {