feat: redux, entities

This commit is contained in:
cha0s 2020-06-21 09:47:44 -05:00
parent 44d76e1ca9
commit 2de1665426
17 changed files with 182 additions and 8 deletions

View File

@ -36,11 +36,13 @@
"html-entities": "1.3.1",
"immer": "^7.0.1",
"memorystore": "^1.6.2",
"normalizr": "^3.6.0",
"prop-types": "^15",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-hot-loader": "^4.12.21",
"react-markdown": "^4.3.1",
"react-redux": "^7.2.0",
"react-tabs": "^3.1.1",
"scwp": "1.x",
"socket.io-redis": "^5.3.0",

View File

@ -16,7 +16,7 @@ const App = () => {
const [typeRenderers] = useState(typeRenderMap());
return (
<TypeRenderersContext.Provider value={typeRenderers}>
<Resource uri="/mama-kitty.entity.json" />
<Resource uri="/resources/cha0s/initial/kitty.entity.json" />
</TypeRenderersContext.Provider>
);
};

View File

@ -3,9 +3,18 @@ import './index.scss';
import {enableMapSet} from 'immer';
import React from 'react';
import {render} from 'react-dom';
import {Provider} from 'react-redux';
import App from './app';
import createStore from './store';
enableMapSet();
render(<App />, document.getElementById('root'));
render(
(
<Provider store={createStore()}>
<App />
</Provider>
),
document.getElementById('root'),
);

View File

@ -1,18 +1,28 @@
import {compose} from '@avocado/core';
import {Entity} from '@avocado/entity';
import contempo from 'contempo';
import React from 'react';
import React, {useEffect} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {fetchEntity, selectEntityByUri} from './state';
import Traits from './traits';
const decorate = compose(
contempo(require('./index.raw.scss')),
contempo(require('./entity.raw.scss')),
);
const json = require('~/../fixtures/rock-projectile.entity.json');
const EntityComponent = (props) => {
const {uri} = props;
const dispatch = useDispatch();
const json = useSelector((state) => selectEntityByUri(state, uri));
useEffect(() => {
if (!json) {
dispatch(fetchEntity(uri));
}
}, [json, uri]);
if (!json) {
return null;
}
const entity = new Entity(json);
const {traits} = json;
return (

View File

@ -1,6 +1,8 @@
import EntityComponent from './component';
import EntityComponent from './entity';
export default {
keys: ['entity', 'entities'],
matcher: /\.entity\.json$/,
Component: EntityComponent,
state: require('./state'),
};

View File

@ -0,0 +1,59 @@
import {normalize, schema} from 'normalizr';
import {
createAsyncThunk,
createEntityAdapter,
createSelector,
createSlice,
} from '@reduxjs/toolkit';
const entitySchema = new schema.Entity('entities', {}, {idAttribute: 'uuid'});
export const fetchEntity = createAsyncThunk(
'entities/fetchEntity',
async (uri) => {
const data = await fetch(uri);
const json = await data.json();
const {entities} = normalize(json, entitySchema);
return entities;
}
);
const adapter = createEntityAdapter({
selectId: (entity) => entity.uuid,
sortComparer: (a, b) => a.uuid.localeCompare(b.uuid),
});
export const {
selectById: selectEntityById,
selectIds: selectEntityIds,
selectEntities: selectEntityEntities,
selectAll: selectAllEntities,
selectTotal: selectTotalEntities
} = adapter.getSelectors((state) => state.entities)
export const selectEntityUris = createSelector(
(state) => state.entities,
(entities) => entities.uris,
);
export const selectEntityByUri = createSelector(
(state) => state.entities,
selectEntityUris,
(_, uri) => uri,
({entities}, uris, uri) => entities[uris[uri]],
)
const slice = createSlice({
name: 'entities',
initialState: adapter.getInitialState({uris: {}}),
reducers: {},
extraReducers: {
[fetchEntity.fulfilled]: (state, action) => {
adapter.upsertMany(state, action.payload.entities);
Object.entries(action.payload.entities)
.forEach(([id, entity]) => state.uris[entity.uri] = id);
},
},
});
export default slice.reducer;

25
src/client/store.js Normal file
View File

@ -0,0 +1,25 @@
import merge from 'deepmerge';
import {combineReducers} from 'redux';
import createCommonStore from '~/common/store';
import {all} from './resources.scwp';
const resources = Object.values(all()).map((M) => M.default);
const reducerMap = resources.reduce(
(r, {keys, state: {'default': reducer}}) => ({...r, [keys[1]]: reducer}),
{}
);
export default function createStore(options = {}) {
return createCommonStore(
merge(
options,
{
middleware: [],
reducer: combineReducers(reducerMap),
},
),
);
}

2
src/common/store/effects.js vendored Normal file
View File

@ -0,0 +1,2 @@
export default {
};

18
src/common/store/index.js Normal file
View File

@ -0,0 +1,18 @@
import merge from 'deepmerge';
import {configureStore, getDefaultMiddleware} from '@reduxjs/toolkit';
import commonMiddleware from './middleware';
export default function createStore(options = {}) {
return configureStore(
merge(
{
middleware: [
...getDefaultMiddleware(),
commonMiddleware,
],
},
options,
),
);
}

View File

@ -0,0 +1,16 @@
import effects from './effects';
const debug = require('debug')('persea:common:store');
export default (store) => (next) => (action) => {
const {meta, payload} = action;
debug("action '%s' dispatched: %o", action.type, {
payload,
meta,
});
const result = next(action);
if (effects[action.type]) {
setTimeout(() => effects[action.type](store, action), 0);
}
return result;
};

View File

@ -1,6 +1,8 @@
import express from 'express';
import httpSession from 'express-session';
import createRoutes from './routes/create';
const MemoryStore = require('memorystore')(httpSession);
let insideSession;
@ -19,6 +21,7 @@ export default function createApp() {
}),
});
app.use(insideSession);
createRoutes(app);
return app;
}

View File

@ -0,0 +1,5 @@
import express from 'express';
export default function createRoutes(app) {
app.use('/resources', express.static('resources'));
}

View File

@ -989,6 +989,13 @@
dependencies:
regenerator-runtime "^0.13.4"
"@babel/runtime@^7.5.5":
version "7.10.3"
resolved "https://npm.i12e.cha0s.io/@babel%2fruntime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364"
integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.1":
version "7.10.1"
resolved "https://npm.i12e.cha0s.io/@babel%2ftemplate/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
@ -6598,6 +6605,11 @@ normalize-url@1.9.1:
query-string "^4.1.0"
sort-keys "^1.0.0"
normalizr@^3.6.0:
version "3.6.0"
resolved "https://npm.i12e.cha0s.io/normalizr/-/normalizr-3.6.0.tgz#b8bbc4546ffe43c1c2200503041642915fcd3e1c"
integrity sha512-25cd8DiDu+pL46KIaxtVVvvEPjGacJgv0yUg950evr62dQ/ks2JO1kf7+Vi5/rMFjaSTSTls7aCnmRlUSljtiA==
notepack.io@~2.1.2:
version "2.1.3"
resolved "https://npm.i12e.cha0s.io/notepack.io/-/notepack.io-2.1.3.tgz#cc904045c751b1a27b2dcfd838d81d0bf3ced923"
@ -7601,7 +7613,7 @@ react-hot-loader@^4.12.21:
shallowequal "^1.1.0"
source-map "^0.7.3"
react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6, react-is@^16.9.0:
version "16.13.1"
resolved "https://npm.i12e.cha0s.io/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -7625,6 +7637,17 @@ react-markdown@^4.3.1:
unist-util-visit "^1.3.0"
xtend "^4.0.1"
react-redux@^7.2.0:
version "7.2.0"
resolved "https://npm.i12e.cha0s.io/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
dependencies:
"@babel/runtime" "^7.5.5"
hoist-non-react-statics "^3.3.0"
loose-envify "^1.4.0"
prop-types "^15.7.2"
react-is "^16.9.0"
react-tabs@^3.1.1:
version "3.1.1"
resolved "https://npm.i12e.cha0s.io/react-tabs/-/react-tabs-3.1.1.tgz#b363a239f76046bb2158875a1e5921b11064052f"