Refactor redux stores
This commit is contained in:
@@ -7,7 +7,7 @@ const sandbox = code => {
|
||||
eval(code);
|
||||
};
|
||||
|
||||
onmessage = e => { // TODO: stop after the first delay() on the initial run
|
||||
onmessage = e => {
|
||||
const lines = e.data.split('\n').map((line, i) => line.replace(/(.+\. *delay *)(\( *\))/g, `$1(${i})`));
|
||||
const { code } = Babel.transform(lines.join('\n'), { presets: ['es2015'] });
|
||||
sandbox(code);
|
||||
|
||||
@@ -30,6 +30,14 @@ const createProjectFile = (name, content) => createFile(name, content, [{
|
||||
|
||||
const createUserFile = (name, content) => createFile(name, content, undefined);
|
||||
|
||||
const isSaved = ({ titles, files, lastTitles, lastFiles }) => {
|
||||
const serialize = (titles, files) => JSON.stringify({
|
||||
titles,
|
||||
files: files.map(({ name, content }) => ({ name, content })),
|
||||
});
|
||||
return serialize(titles, files) === serialize(lastTitles, lastFiles);
|
||||
};
|
||||
|
||||
export {
|
||||
classes,
|
||||
distance,
|
||||
@@ -38,4 +46,5 @@ export {
|
||||
createFile,
|
||||
createProjectFile,
|
||||
createUserFile,
|
||||
isSaved,
|
||||
};
|
||||
|
||||
@@ -3,10 +3,7 @@ import Cookies from 'js-cookie';
|
||||
import { connect } from 'react-redux';
|
||||
import Promise from 'bluebird';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import AutosizeInput from 'react-input-autosize';
|
||||
import queryString from 'query-string';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import faPlus from '@fortawesome/fontawesome-free-solid/faPlus';
|
||||
import {
|
||||
BaseComponent,
|
||||
CodeEditor,
|
||||
@@ -32,7 +29,6 @@ class App extends BaseComponent {
|
||||
this.state = {
|
||||
navigatorOpened: true,
|
||||
workspaceWeights: [1, 2, 2],
|
||||
editorTabIndex: -1,
|
||||
};
|
||||
|
||||
this.codeEditorRef = React.createRef();
|
||||
@@ -79,11 +75,12 @@ class App extends BaseComponent {
|
||||
|
||||
toggleHistoryBlock(enable = !this.unblock) {
|
||||
if (enable) {
|
||||
const { saved } = this.props.current;
|
||||
const warningMessage = 'Are you sure you want to discard changes?';
|
||||
window.onbeforeunload = () => this.isSaved() ? undefined : warningMessage;
|
||||
window.onbeforeunload = () => saved ? undefined : warningMessage;
|
||||
this.unblock = this.props.history.block((location) => {
|
||||
if (location.pathname === this.props.location.pathname) return;
|
||||
if (!this.isSaved()) return warningMessage;
|
||||
if (!saved) return warningMessage;
|
||||
});
|
||||
} else {
|
||||
window.onbeforeunload = undefined;
|
||||
@@ -189,11 +186,11 @@ class App extends BaseComponent {
|
||||
selectDefaultTab() {
|
||||
const { ext } = this.props.env;
|
||||
const { files } = this.props.current;
|
||||
let editorTabIndex = files.findIndex(file => extension(file.name) === 'json');
|
||||
if (!~editorTabIndex) files.findIndex(file => extension(file.name) === ext);
|
||||
if (!~editorTabIndex) editorTabIndex = files.findIndex(file => exts.includes(extension(file.name)));
|
||||
if (!~editorTabIndex) editorTabIndex = Math.min(0, files.length - 1);
|
||||
this.handleChangeEditorTabIndex(editorTabIndex);
|
||||
const editingFile = files.find(file => extension(file.name) === 'json') ||
|
||||
files.find(file => extension(file.name) === ext) ||
|
||||
files.find(file => exts.includes(extension(file.name))) ||
|
||||
files[files.length - 1];
|
||||
this.props.setEditingFile(editingFile);
|
||||
}
|
||||
|
||||
handleChangeWorkspaceWeights(workspaceWeights) {
|
||||
@@ -201,67 +198,15 @@ class App extends BaseComponent {
|
||||
this.codeEditorRef.current.getWrappedInstance().handleResize();
|
||||
}
|
||||
|
||||
handleChangeEditorTabIndex(editorTabIndex) {
|
||||
const { files } = this.props.current;
|
||||
if (editorTabIndex === files.length) this.handleAddFile();
|
||||
this.setState({ editorTabIndex });
|
||||
this.props.shouldBuild();
|
||||
}
|
||||
|
||||
handleAddFile() {
|
||||
const { ext } = this.props.env;
|
||||
const { files } = this.props.current;
|
||||
const language = languages.find(language => language.ext === ext);
|
||||
const file = { ...language.skeleton };
|
||||
let count = 0;
|
||||
while (files.some(existingFile => existingFile.name === file.name)) file.name = `code-${++count}.${ext}`;
|
||||
this.props.addFile(file);
|
||||
}
|
||||
|
||||
handleRenameFile(e) {
|
||||
const { value } = e.target;
|
||||
const { editorTabIndex } = this.state;
|
||||
this.props.renameFile(editorTabIndex, value);
|
||||
}
|
||||
|
||||
handleDeleteFile() {
|
||||
const { editorTabIndex } = this.state;
|
||||
const { files } = this.props.current;
|
||||
this.handleChangeEditorTabIndex(Math.min(editorTabIndex, files.length - 2));
|
||||
this.props.deleteFile(editorTabIndex);
|
||||
}
|
||||
|
||||
toggleNavigatorOpened(navigatorOpened = !this.state.navigatorOpened) {
|
||||
this.setState({ navigatorOpened });
|
||||
}
|
||||
|
||||
isSaved() {
|
||||
const { titles, files, lastTitles, lastFiles } = this.props.current;
|
||||
const serialize = (titles, files) => JSON.stringify({
|
||||
titles,
|
||||
files: files.map(({ name, content }) => ({ name, content })),
|
||||
});
|
||||
return serialize(titles, files) === serialize(lastTitles, lastFiles);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navigatorOpened, workspaceWeights, editorTabIndex } = this.state;
|
||||
const { navigatorOpened, workspaceWeights } = this.state;
|
||||
|
||||
const { files, titles, description } = this.props.current;
|
||||
const saved = this.isSaved();
|
||||
const { titles, description, saved } = this.props.current;
|
||||
const title = `${saved ? '' : '(Unsaved) '}${titles.join(' - ')}`;
|
||||
const file = files[editorTabIndex];
|
||||
|
||||
const editorTitles = files.map(file => file.name);
|
||||
if (file) {
|
||||
editorTitles[editorTabIndex] = (
|
||||
<AutosizeInput className={styles.input_title} value={file.name}
|
||||
onClick={e => e.stopPropagation()} onChange={e => this.handleRenameFile(e)} />
|
||||
);
|
||||
}
|
||||
editorTitles.push(
|
||||
<FontAwesomeIcon fixedWidth icon={faPlus} />,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
@@ -269,17 +214,16 @@ class App extends BaseComponent {
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
</Helmet>
|
||||
<Header className={styles.header} onClickTitleBar={() => this.toggleNavigatorOpened()} saved={saved}
|
||||
navigatorOpened={navigatorOpened} loadScratchPapers={() => this.loadScratchPapers()} file={file}
|
||||
<Header className={styles.header} onClickTitleBar={() => this.toggleNavigatorOpened()}
|
||||
navigatorOpened={navigatorOpened} loadScratchPapers={() => this.loadScratchPapers()}
|
||||
ignoreHistoryBlock={this.ignoreHistoryBlock} />
|
||||
<ResizableContainer className={styles.workspace} horizontal weights={workspaceWeights}
|
||||
visibles={[navigatorOpened, true, true]}
|
||||
onChangeWeights={weights => this.handleChangeWorkspaceWeights(weights)}>
|
||||
<Navigator />
|
||||
<VisualizationViewer className={styles.visualization_viewer} />
|
||||
<TabContainer className={styles.editor_tab_container} titles={editorTitles} tabIndex={editorTabIndex}
|
||||
onChangeTabIndex={tabIndex => this.handleChangeEditorTabIndex(tabIndex)}>
|
||||
<CodeEditor ref={this.codeEditorRef} file={file} onClickDelete={() => this.handleDeleteFile()} />
|
||||
<TabContainer className={styles.editor_tab_container}>
|
||||
<CodeEditor ref={this.codeEditorRef} />
|
||||
</TabContainer>
|
||||
</ResizableContainer>
|
||||
<ToastContainer className={styles.toast_container} />
|
||||
|
||||
@@ -60,16 +60,6 @@ button {
|
||||
}
|
||||
|
||||
.editor_tab_container {
|
||||
.input_title {
|
||||
input {
|
||||
&:hover,
|
||||
&:focus {
|
||||
margin: -4px;
|
||||
padding: 4px;
|
||||
background-color: $theme-normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { languages } from '/common/config';
|
||||
import { Button, Ellipsis, FoldableAceEditor } from '/components';
|
||||
import styles from './stylesheet.scss';
|
||||
|
||||
@connect(({ env, player }) => ({ env, player }), actions, null, { withRef: true })
|
||||
@connect(({ current, env, player }) => ({ current, env, player }), actions, null, { withRef: true })
|
||||
class CodeEditor extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -16,24 +16,19 @@ class CodeEditor extends React.Component {
|
||||
this.aceEditorRef = React.createRef();
|
||||
}
|
||||
|
||||
handleChangeCode(code) {
|
||||
const { file } = this.props;
|
||||
this.props.modifyFile({ ...file, content: code });
|
||||
if (extension(file.name) === 'md') this.props.shouldBuild();
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
this.aceEditorRef.current.editor.resize();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, file, onClickDelete } = this.props;
|
||||
const { className } = this.props;
|
||||
const { editingFile } = this.props.current;
|
||||
const { user } = this.props.env;
|
||||
const { lineIndicator, buildAt } = this.props.player;
|
||||
const { lineIndicator } = this.props.player;
|
||||
|
||||
if (!file) return null;
|
||||
if (!editingFile) return null;
|
||||
|
||||
const fileExt = extension(file.name);
|
||||
const fileExt = extension(editingFile.name);
|
||||
const language = languages.find(language => language.ext === fileExt);
|
||||
const mode = language ? language.mode :
|
||||
fileExt === 'md' ? 'markdown' :
|
||||
@@ -49,7 +44,7 @@ class CodeEditor extends React.Component {
|
||||
theme="tomorrow_night_eighties"
|
||||
name="code_editor"
|
||||
editorProps={{ $blockScrolling: true }}
|
||||
onChange={code => this.handleChangeCode(code)}
|
||||
onChange={code => this.props.modifyFile(editingFile, code)}
|
||||
markers={lineIndicator ? [{
|
||||
startRow: lineIndicator.lineNumber,
|
||||
startCol: 0,
|
||||
@@ -60,12 +55,11 @@ class CodeEditor extends React.Component {
|
||||
inFront: true,
|
||||
_key: lineIndicator.cursor,
|
||||
}] : []}
|
||||
foldAt={buildAt}
|
||||
value={file.content} />
|
||||
value={editingFile.content} />
|
||||
<div className={classes(styles.contributors_viewer, className)}>
|
||||
<span className={classes(styles.contributor, styles.label)}>Contributed by</span>
|
||||
{
|
||||
(file.contributors || [user || { login: 'guest', avatar_url: faUser }]).map(contributor => (
|
||||
(editingFile.contributors || [user || { login: 'guest', avatar_url: faUser }]).map(contributor => (
|
||||
<Button className={styles.contributor} icon={contributor.avatar_url} key={contributor.login}
|
||||
href={`https://github.com/${contributor.login}`}>
|
||||
{contributor.login}
|
||||
@@ -74,8 +68,8 @@ class CodeEditor extends React.Component {
|
||||
}
|
||||
<div className={styles.empty}>
|
||||
<div className={styles.empty} />
|
||||
<Button className={styles.delete} icon={faTrashAlt} primary onClick={onClickDelete}
|
||||
confirmNeeded>
|
||||
<Button className={styles.delete} icon={faTrashAlt} primary confirmNeeded
|
||||
onClick={() => this.props.deleteFile(editingFile)}>
|
||||
<Ellipsis>Delete File</Ellipsis>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'brace/mode/plain_text';
|
||||
import 'brace/mode/markdown';
|
||||
@@ -8,19 +9,23 @@ import 'brace/mode/c_cpp';
|
||||
import 'brace/mode/java';
|
||||
import 'brace/theme/tomorrow_night_eighties';
|
||||
import 'brace/ext/searchbox';
|
||||
import { actions } from '/reducers';
|
||||
|
||||
@connect(({ current }) => ({ current }), actions)
|
||||
class FoldableAceEditor extends AceEditor {
|
||||
componentDidMount() {
|
||||
super.componentDidMount();
|
||||
|
||||
this.foldTracers();
|
||||
const { shouldBuild } = this.props.current;
|
||||
if (shouldBuild) this.foldTracers();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
super.componentDidUpdate(prevProps, prevState, snapshot);
|
||||
|
||||
if (prevProps.foldAt !== this.props.foldAt) {
|
||||
this.foldTracers();
|
||||
const { editingFile, shouldBuild } = this.props.current;
|
||||
if (editingFile !== prevProps.current.editingFile) {
|
||||
if (shouldBuild) this.foldTracers();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ class Header extends BaseComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, onClickTitleBar, navigatorOpened, saved, file } = this.props;
|
||||
const { scratchPaper, titles } = this.props.current;
|
||||
const { className, onClickTitleBar, navigatorOpened } = this.props;
|
||||
const { scratchPaper, titles, saved } = this.props.current;
|
||||
const { ext, user } = this.props.env;
|
||||
|
||||
const permitted = this.hasPermission();
|
||||
@@ -174,7 +174,7 @@ class Header extends BaseComponent {
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<Player className={styles.section} file={file} />
|
||||
<Player className={styles.section} />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { actions } from '/reducers';
|
||||
import { BaseComponent, Button, ProgressBar } from '/components';
|
||||
import styles from './stylesheet.scss';
|
||||
|
||||
@connect(({ player }) => ({ player }), actions)
|
||||
@connect(({ current, player }) => ({ current, player }), actions)
|
||||
class Player extends BaseComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -30,15 +30,14 @@ class Player extends BaseComponent {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { file } = this.props;
|
||||
this.build(file);
|
||||
const { editingFile, shouldBuild } = this.props.current;
|
||||
if (shouldBuild) this.build(editingFile);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { file } = nextProps;
|
||||
const { buildAt } = nextProps.player;
|
||||
if (buildAt !== this.props.player.buildAt) {
|
||||
this.build(file);
|
||||
const { editingFile, shouldBuild } = nextProps.current;
|
||||
if (editingFile !== this.props.current.editingFile) {
|
||||
if (shouldBuild) this.build(editingFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,13 +144,15 @@ class Player extends BaseComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, file } = this.props;
|
||||
const { className } = this.props;
|
||||
const { editingFile } = this.props.current;
|
||||
const { chunks, cursor } = this.props.player;
|
||||
const { speed, playing, building } = this.state;
|
||||
|
||||
return (
|
||||
<div className={classes(styles.player, className)}>
|
||||
<Button icon={faWrench} primary disabled={building} inProgress={building} onClick={() => this.build(file)}>
|
||||
<Button icon={faWrench} primary disabled={building} inProgress={building}
|
||||
onClick={() => this.build(editingFile)}>
|
||||
{building ? 'Building' : 'Build'}
|
||||
</Button>
|
||||
{
|
||||
|
||||
@@ -1,26 +1,50 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import AutosizeInput from 'react-input-autosize';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import faPlus from '@fortawesome/fontawesome-free-solid/faPlus';
|
||||
import { classes } from '/common/util';
|
||||
import { actions } from '/reducers';
|
||||
import { languages } from '/common/config';
|
||||
import styles from './stylesheet.scss';
|
||||
|
||||
@connect(({ current, env }) => ({ current, env }), actions)
|
||||
class TabContainer extends React.Component {
|
||||
handleAddFile() {
|
||||
const { ext } = this.props.env;
|
||||
const { files } = this.props.current;
|
||||
const language = languages.find(language => language.ext === ext);
|
||||
const newFile = { ...language.skeleton };
|
||||
let count = 0;
|
||||
while (files.some(file => file.name === newFile.name)) newFile.name = `code-${++count}.${ext}`;
|
||||
this.props.addFile(newFile);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, children, titles, tabIndex, onChangeTabIndex } = this.props;
|
||||
const { className, children } = this.props;
|
||||
const { editingFile, files } = this.props.current;
|
||||
|
||||
return (
|
||||
<div className={classes(styles.tab_container, className)}>
|
||||
<div className={styles.tab_bar}>
|
||||
<div className={classes(styles.title, styles.fake)} />
|
||||
{
|
||||
titles.map((title, i) => {
|
||||
const selected = i === tabIndex;
|
||||
return (
|
||||
<div className={classes(styles.title, selected && styles.selected)} key={i}
|
||||
onClick={() => onChangeTabIndex(i)}>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
files.map((file, i) => file === editingFile ? (
|
||||
<div className={classes(styles.title, styles.selected)} key={i}
|
||||
onClick={() => this.props.setEditingFile(file)}>
|
||||
<AutosizeInput className={styles.input_title} value={file.name}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onChange={e => this.props.renameFile(file, e.target.value)} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.title} key={i} onClick={() => this.props.setEditingFile(file)}>
|
||||
{file.name}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<div className={styles.title} onClick={() => this.handleAddFile()}>
|
||||
<FontAwesomeIcon fixedWidth icon={faPlus} />
|
||||
</div>
|
||||
<div className={classes(styles.title, styles.fake)} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
|
||||
@@ -25,6 +25,17 @@
|
||||
margin: 0;
|
||||
border-bottom: 1px solid $theme-light;
|
||||
|
||||
.input_title {
|
||||
input {
|
||||
&:hover,
|
||||
&:focus {
|
||||
margin: -4px;
|
||||
padding: 4px;
|
||||
background-color: $theme-normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-left: 1px solid $theme-light;
|
||||
border-right: 1px solid $theme-light;
|
||||
@@ -56,4 +67,4 @@
|
||||
background-color: $theme-dark;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,34 @@
|
||||
import { combineActions, createAction, handleActions } from 'redux-actions';
|
||||
import { README_MD } from '/files';
|
||||
import { extension, isSaved } from '/common/util';
|
||||
|
||||
const prefix = 'CURRENT';
|
||||
|
||||
const setHome = createAction(`${prefix}/SET_HOME`, () => defaultState);
|
||||
const setAlgorithm = createAction(`${prefix}/SET_ALGORITHM`, ({ categoryKey, categoryName, algorithmKey, algorithmName, files, description }) => {
|
||||
const titles = [categoryName, algorithmName];
|
||||
return {
|
||||
algorithm: { categoryKey, algorithmKey },
|
||||
scratchPaper: undefined,
|
||||
titles,
|
||||
files,
|
||||
lastTitles: titles,
|
||||
lastFiles: files,
|
||||
description,
|
||||
};
|
||||
});
|
||||
const setScratchPaper = createAction(`${prefix}/SET_SCRATCH_PAPER`, ({ login, gistId, title, files }) => {
|
||||
const titles = ['Scratch Paper', title];
|
||||
return {
|
||||
algorithm: undefined,
|
||||
scratchPaper: { login, gistId },
|
||||
titles,
|
||||
files,
|
||||
lastTitles: titles,
|
||||
lastFiles: files,
|
||||
description: homeDescription,
|
||||
};
|
||||
});
|
||||
const setAlgorithm = createAction(`${prefix}/SET_ALGORITHM`, ({ categoryKey, categoryName, algorithmKey, algorithmName, files, description }) => ({
|
||||
algorithm: { categoryKey, algorithmKey },
|
||||
titles: [categoryName, algorithmName],
|
||||
files,
|
||||
description,
|
||||
}));
|
||||
const setScratchPaper = createAction(`${prefix}/SET_SCRATCH_PAPER`, ({ login, gistId, title, files }) => ({
|
||||
scratchPaper: { login, gistId },
|
||||
titles: ['Scratch Paper', title],
|
||||
files,
|
||||
description: homeDescription,
|
||||
}));
|
||||
const setEditingFile = createAction(`${prefix}/SET_EDITING_FILE`, file => ({ file }));
|
||||
const modifyTitle = createAction(`${prefix}/MODIFY_TITLE`, title => ({ title }));
|
||||
const addFile = createAction(`${prefix}/ADD_FILE`, file => ({ file }));
|
||||
const modifyFile = createAction(`${prefix}/MODIFY_FILE`, file => ({ file }));
|
||||
const deleteFile = createAction(`${prefix}/DELETE_FILE`, index => ({ index }));
|
||||
const renameFile = createAction(`${prefix}/RENAME_FILE`, (index, name) => ({ index, name }));
|
||||
const renameFile = createAction(`${prefix}/RENAME_FILE`, (file, name) => ({ file, name }));
|
||||
const modifyFile = createAction(`${prefix}/MODIFY_FILE`, (file, content) => ({ file, content }));
|
||||
const deleteFile = createAction(`${prefix}/DELETE_FILE`, file => ({ file }));
|
||||
|
||||
export const actions = {
|
||||
setHome,
|
||||
setAlgorithm,
|
||||
setScratchPaper,
|
||||
setEditingFile,
|
||||
modifyTitle,
|
||||
addFile,
|
||||
modifyFile,
|
||||
@@ -59,6 +50,9 @@ const defaultState = {
|
||||
lastTitles: homeTitles,
|
||||
lastFiles: homeFiles,
|
||||
description: homeDescription,
|
||||
editingFile: undefined,
|
||||
shouldBuild: true,
|
||||
saved: true,
|
||||
};
|
||||
|
||||
export default handleActions({
|
||||
@@ -66,35 +60,85 @@ export default handleActions({
|
||||
setHome,
|
||||
setAlgorithm,
|
||||
setScratchPaper,
|
||||
)]: (state, { payload }) => ({
|
||||
...state,
|
||||
...payload,
|
||||
}),
|
||||
[modifyTitle]: (state, { payload }) => {
|
||||
const { title } = payload;
|
||||
)]: (state, { payload }) => {
|
||||
const { algorithm, scratchPaper, titles, files, description } = payload;
|
||||
return {
|
||||
...state,
|
||||
algorithm,
|
||||
scratchPaper,
|
||||
titles,
|
||||
files,
|
||||
lastTitles: titles,
|
||||
lastFiles: files,
|
||||
description,
|
||||
editingFile: undefined,
|
||||
shouldBuild: true,
|
||||
saved: true,
|
||||
};
|
||||
},
|
||||
[setEditingFile]: (state, { payload }) => {
|
||||
const { file } = payload;
|
||||
return {
|
||||
...state,
|
||||
editingFile: file,
|
||||
shouldBuild: true,
|
||||
};
|
||||
},
|
||||
[modifyTitle]: (state, { payload }) => {
|
||||
const { title } = payload;
|
||||
const newState = {
|
||||
...state,
|
||||
titles: [state.titles[0], title],
|
||||
};
|
||||
return {
|
||||
...newState,
|
||||
saved: isSaved(newState),
|
||||
};
|
||||
},
|
||||
[addFile]: (state, { payload }) => {
|
||||
const { file } = payload;
|
||||
const files = [...state.files, file];
|
||||
return { ...state, files };
|
||||
const newState = {
|
||||
...state,
|
||||
files: [...state.files, file],
|
||||
editingFile: file,
|
||||
shouldBuild: true,
|
||||
};
|
||||
return {
|
||||
...newState,
|
||||
saved: isSaved(newState),
|
||||
};
|
||||
},
|
||||
[modifyFile]: (state, { payload }) => {
|
||||
const { file } = payload;
|
||||
const files = state.files.map(oldFile => oldFile.name === file.name ? file : oldFile);
|
||||
return { ...state, files };
|
||||
[combineActions(
|
||||
renameFile,
|
||||
modifyFile,
|
||||
)]: (state, { payload }) => {
|
||||
const { file, ...update } = payload;
|
||||
const editingFile = { ...file, ...update };
|
||||
const newState = {
|
||||
...state,
|
||||
files: state.files.map(oldFile => oldFile === file ? editingFile : oldFile),
|
||||
editingFile,
|
||||
shouldBuild: extension(editingFile.name) === 'md',
|
||||
};
|
||||
return {
|
||||
...newState,
|
||||
saved: isSaved(newState),
|
||||
};
|
||||
},
|
||||
[deleteFile]: (state, { payload }) => {
|
||||
const { index } = payload;
|
||||
const files = state.files.filter((file, i) => i !== index);
|
||||
return { ...state, files };
|
||||
},
|
||||
[renameFile]: (state, { payload }) => {
|
||||
const { index, name } = payload;
|
||||
const files = state.files.map((file, i) => i === index ? { ...file, name } : file);
|
||||
return { ...state, files };
|
||||
const { file } = payload;
|
||||
const index = state.files.indexOf(file);
|
||||
const files = state.files.filter(oldFile => oldFile !== file);
|
||||
const editingFile = files[Math.min(index, files.length - 1)];
|
||||
const newState = {
|
||||
...state,
|
||||
files,
|
||||
editingFile,
|
||||
shouldBuild: true,
|
||||
};
|
||||
return {
|
||||
...newState,
|
||||
saved: isSaved(newState),
|
||||
};
|
||||
},
|
||||
}, defaultState);
|
||||
|
||||
@@ -2,20 +2,17 @@ import { combineActions, createAction, handleActions } from 'redux-actions';
|
||||
|
||||
const prefix = 'PLAYER';
|
||||
|
||||
const shouldBuild = createAction(`${prefix}/SHOULD_BUILD`, () => ({ buildAt: Date.now() }));
|
||||
const setChunks = createAction(`${prefix}/SET_CHUNKS`, chunks => ({ chunks }));
|
||||
const setCursor = createAction(`${prefix}/SET_CURSOR`, cursor => ({ cursor }));
|
||||
const setLineIndicator = createAction(`${prefix}/SET_LINE_INDICATOR`, lineIndicator => ({ lineIndicator }));
|
||||
|
||||
export const actions = {
|
||||
shouldBuild,
|
||||
setChunks,
|
||||
setCursor,
|
||||
setLineIndicator,
|
||||
};
|
||||
|
||||
const defaultState = {
|
||||
buildAt: 0,
|
||||
chunks: [],
|
||||
cursor: 0,
|
||||
lineIndicator: undefined,
|
||||
@@ -23,7 +20,6 @@ const defaultState = {
|
||||
|
||||
export default handleActions({
|
||||
[combineActions(
|
||||
shouldBuild,
|
||||
setChunks,
|
||||
setCursor,
|
||||
setLineIndicator,
|
||||
|
||||
Reference in New Issue
Block a user