From cde65f2bbbdaff56880972a93231dad080bb6d12 Mon Sep 17 00:00:00 2001 From: teramotodaiki Date: Tue, 19 Nov 2019 14:48:15 +0900 Subject: [PATCH] feat: sandbox on store --- src/CodeCell.tsx | 23 +-------------- src/CodeView.tsx | 68 +++++++++++++++++++++----------------------- src/root.tsx | 14 +++++---- src/store/index.ts | 17 ++++++++--- src/store/sandbox.ts | 61 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 67 deletions(-) create mode 100644 src/store/sandbox.ts diff --git a/src/CodeCell.tsx b/src/CodeCell.tsx index d7565b8..065edd6 100644 --- a/src/CodeCell.tsx +++ b/src/CodeCell.tsx @@ -18,16 +18,9 @@ export interface CodeCellProps { value: string; title?: string; onUpdate: OnUpdate; - onGame: () => void; } -export function CodeCell({ - id, - value, - title, - onUpdate, - onGame -}: CodeCellProps) { +export function CodeCell({ id, value, title, onUpdate }: CodeCellProps) { const rootRef = React.useRef(null); const editorRef = React.useRef(); const [floating, setFloating] = React.useState(false); @@ -70,21 +63,7 @@ export function CodeCell({ setFloating(true); }); - let previousCode = value; - let blurTimerHandle = 0; editor.onDidBlurEditorText(() => { - window.cancelIdleCallback(blurTimerHandle); - blurTimerHandle = window.requestIdleCallback( - () => { - const e = document.activeElement; - if (!e || e.tagName !== 'IFRAME') return; - const coffee = editor.getValue(); - if (previousCode === coffee) return; - onGame(); - previousCode = coffee; - }, - { timeout: 2000 } - ); setFloating(false); }); diff --git a/src/CodeView.tsx b/src/CodeView.tsx index 5a2ef7f..fc0a2fd 100644 --- a/src/CodeView.tsx +++ b/src/CodeView.tsx @@ -1,24 +1,26 @@ import classNames from 'classnames'; import { CoffeeScript } from 'coffeescript'; import * as React from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { appendEmptyLine } from './append-empty-line'; import { build } from './build'; import { blockify, cellify, ICodeCell } from './cellify'; import { CodeCell } from './CodeCell'; import './completion'; -import { Sandbox } from './sandbox'; +import { actions, SS } from './store'; import style from './styles/code-view.scss'; -import { TextCell } from './TextCell'; import flex from './styles/flex.scss'; +import { TextCell } from './TextCell'; export interface CodeViewProps { code: string; + handleRun: () => void; } export type OnUpdate = (payload: { id: string; value: string }) => void; -let sandbox = new Sandbox(); -export function CodeView({ code }: CodeViewProps) { +export function CodeView({ code, handleRun }: CodeViewProps) { + const dispatch = useDispatch(); const cellsRef = React.useRef(cellify(code)); // Notice: mutable for (let cell of cellsRef.current) { @@ -48,22 +50,28 @@ export function CodeView({ code }: CodeViewProps) { cell.value = value; cell.nodes = blockify(value); } - }, []); - const onGame = React.useCallback(() => { - // Build && Run - sandbox.update([ - { - name: 'modules/プレイヤー.js', - type: 'application/javascript', - code: build(cellsRef.current) - } - ]); - sandbox.run(); + dispatch( + actions.writeFiles([ + { + name: 'modules/プレイヤー.js', + type: 'application/javascript', + code: build(cells) + } + ]) + ); }, []); React.useEffect(() => { - onGame(); + dispatch( + actions.writeFiles([ + { + name: 'modules/プレイヤー.js', + type: 'application/javascript', + code: build(cellsRef.current) + } + ]) + ); }, []); const fileInfo = { @@ -79,7 +87,7 @@ export function CodeView({ code }: CodeViewProps) {
{fileInfo.name.ja}
- +
{cellsRef.current.map(cell => cell.type === 'code' ? ( @@ -89,7 +97,6 @@ export function CodeView({ code }: CodeViewProps) { value={cell.value} title={cell.meta} onUpdate={onUpdate} - onGame={onGame} /> ) : ( void; +} - const onClick = React.useCallback(() => { - setSent(true); - setTimeout(() => { - setSent(false); - }, 2000); - }, []); +function PaperPlane({ handleRun }: PaperPlaneProps) { + const version = useSelector((state: SS) => state.sandbox.version); + const rv = useSelector((state: SS) => state.sandbox.runningVersion); return ( -
+
✈
); diff --git a/src/root.tsx b/src/root.tsx index cd1a1b5..ea4312d 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -1,16 +1,16 @@ import classNames from 'classnames'; import * as React from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { CodeView } from './CodeView'; -import flex from './styles/flex.scss'; -import font from './styles/font.scss'; -import region from './styles/region.scss'; import { FloorView } from './FloorView'; import { Footer } from './Footer'; import { Header } from './Header'; import { MapView } from './MapView'; -import { EditorMode, SS } from './store'; +import { actions, EditorMode, SS } from './store'; import { StoreView } from './StoreView'; +import flex from './styles/flex.scss'; +import font from './styles/font.scss'; +import region from './styles/region.scss'; import { Transition } from './Transition'; export interface RootProps { @@ -18,6 +18,7 @@ export interface RootProps { } export function Root({ code }: RootProps) { + const dispatch = useDispatch(); const iframeRef = React.useRef(null); const [bringFront, setBringFront] = React.useState(false); const focusGame = React.useCallback(() => { @@ -25,6 +26,7 @@ export function Root({ code }: RootProps) { iframeRef.current.focus(); } setBringFront(true); + dispatch(actions.runSanbox()); }, []); const unfocusGame = React.useCallback(() => { setBringFront(false); @@ -63,7 +65,7 @@ export function Root({ code }: RootProps) { className={region.codeView} exiting={region.exiting} > - +
diff --git a/src/store/index.ts b/src/store/index.ts index eb6a12e..2f0f48a 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,7 +1,12 @@ import { applyMiddleware, combineReducers, createStore, Reducer } from 'redux'; -import { combineEpics, createEpicMiddleware } from 'redux-observable'; +import { + combineEpics, + createEpicMiddleware, + StateObservable +} from 'redux-observable'; import * as floor from './floor'; import * as mode from './mode'; +import * as sandbox from './sandbox'; export * from './enums'; @@ -12,17 +17,21 @@ export type SS = StoreState; // alias export type Store = ReturnType; +export type S$ = StateObservable; + export const rootReducer = combineReducers({ ...floor.reducers, - ...mode.reducers + ...mode.reducers, + ...sandbox.reducers }); export const actions = { ...floor.actions, - ...mode.actions + ...mode.actions, + ...sandbox.actions }; -export const rootEpic = combineEpics(floor.epic); +export const rootEpic = combineEpics(floor.epic, sandbox.epic); export function createGamebookStore() { const epicMiddleware = createEpicMiddleware(); diff --git a/src/store/sandbox.ts b/src/store/sandbox.ts new file mode 100644 index 0000000..e683393 --- /dev/null +++ b/src/store/sandbox.ts @@ -0,0 +1,61 @@ +import { combineEpics } from 'redux-observable'; +import { NEVER } from 'rxjs'; +import { + distinctUntilChanged, + filter, + map, + mergeMap, + tap +} from 'rxjs/operators'; +import actionCreatorFactory from 'typescript-fsa'; +import { S$ } from '.'; +import { IFile, Sandbox } from '../sandbox'; +import { reducerWithImmer } from './reducerWithImmer'; + +export const initialState = { + instance: new Sandbox(), + runningVersion: 0, + version: 0 +}; + +const actionCreator = actionCreatorFactory('gamebook'); + +export const actions = { + runSanbox: actionCreator('RUN_SANDBOX'), + writeFiles: actionCreator('WRITE_FILES') +}; + +const sandbox = reducerWithImmer(initialState) + .case(actions.runSanbox, draft => { + draft.runningVersion = draft.version; + }) + .case(actions.writeFiles, draft => { + draft.version += 1; + }) + .toReducer(); + +export default sandbox; + +export const reducers = { + sandbox +}; + +export const epic = combineEpics( + (action$, store$: S$) => + action$.pipe( + filter(actions.writeFiles.match), + tap(action => { + store$.value.sandbox.instance.update(action.payload); + }), + mergeMap(() => NEVER) + ), + (action$, store$: S$) => + store$.pipe( + map(state => state.sandbox.runningVersion), + distinctUntilChanged(), + tap(() => { + store$.value.sandbox.instance.run(); + }), + mergeMap(() => NEVER) + ) +);