feat: redux, entities
This commit is contained in:
parent
44d76e1ca9
commit
2de1665426
|
@ -36,11 +36,13 @@
|
||||||
"html-entities": "1.3.1",
|
"html-entities": "1.3.1",
|
||||||
"immer": "^7.0.1",
|
"immer": "^7.0.1",
|
||||||
"memorystore": "^1.6.2",
|
"memorystore": "^1.6.2",
|
||||||
|
"normalizr": "^3.6.0",
|
||||||
"prop-types": "^15",
|
"prop-types": "^15",
|
||||||
"react": "16.8.6",
|
"react": "16.8.6",
|
||||||
"react-dom": "16.8.6",
|
"react-dom": "16.8.6",
|
||||||
"react-hot-loader": "^4.12.21",
|
"react-hot-loader": "^4.12.21",
|
||||||
"react-markdown": "^4.3.1",
|
"react-markdown": "^4.3.1",
|
||||||
|
"react-redux": "^7.2.0",
|
||||||
"react-tabs": "^3.1.1",
|
"react-tabs": "^3.1.1",
|
||||||
"scwp": "1.x",
|
"scwp": "1.x",
|
||||||
"socket.io-redis": "^5.3.0",
|
"socket.io-redis": "^5.3.0",
|
||||||
|
|
|
@ -16,7 +16,7 @@ const App = () => {
|
||||||
const [typeRenderers] = useState(typeRenderMap());
|
const [typeRenderers] = useState(typeRenderMap());
|
||||||
return (
|
return (
|
||||||
<TypeRenderersContext.Provider value={typeRenderers}>
|
<TypeRenderersContext.Provider value={typeRenderers}>
|
||||||
<Resource uri="/mama-kitty.entity.json" />
|
<Resource uri="/resources/cha0s/initial/kitty.entity.json" />
|
||||||
</TypeRenderersContext.Provider>
|
</TypeRenderersContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,9 +3,18 @@ import './index.scss';
|
||||||
import {enableMapSet} from 'immer';
|
import {enableMapSet} from 'immer';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {render} from 'react-dom';
|
import {render} from 'react-dom';
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
|
|
||||||
import App from './app';
|
import App from './app';
|
||||||
|
import createStore from './store';
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
|
|
||||||
render(<App />, document.getElementById('root'));
|
render(
|
||||||
|
(
|
||||||
|
<Provider store={createStore()}>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
|
),
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
import {compose} from '@avocado/core';
|
import {compose} from '@avocado/core';
|
||||||
import {Entity} from '@avocado/entity';
|
import {Entity} from '@avocado/entity';
|
||||||
import contempo from 'contempo';
|
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';
|
import Traits from './traits';
|
||||||
|
|
||||||
const decorate = compose(
|
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 EntityComponent = (props) => {
|
||||||
const {uri} = 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 entity = new Entity(json);
|
||||||
const {traits} = json;
|
const {traits} = json;
|
||||||
return (
|
return (
|
|
@ -1,6 +1,8 @@
|
||||||
import EntityComponent from './component';
|
import EntityComponent from './entity';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
keys: ['entity', 'entities'],
|
||||||
matcher: /\.entity\.json$/,
|
matcher: /\.entity\.json$/,
|
||||||
Component: EntityComponent,
|
Component: EntityComponent,
|
||||||
|
state: require('./state'),
|
||||||
};
|
};
|
||||||
|
|
59
src/client/resources/entity/state.js
Normal file
59
src/client/resources/entity/state.js
Normal 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
25
src/client/store.js
Normal 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
2
src/common/store/effects.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export default {
|
||||||
|
};
|
18
src/common/store/index.js
Normal file
18
src/common/store/index.js
Normal 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
16
src/common/store/middleware.js
Normal file
16
src/common/store/middleware.js
Normal 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;
|
||||||
|
};
|
|
@ -1,6 +1,8 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import httpSession from 'express-session';
|
import httpSession from 'express-session';
|
||||||
|
|
||||||
|
import createRoutes from './routes/create';
|
||||||
|
|
||||||
const MemoryStore = require('memorystore')(httpSession);
|
const MemoryStore = require('memorystore')(httpSession);
|
||||||
|
|
||||||
let insideSession;
|
let insideSession;
|
||||||
|
@ -19,6 +21,7 @@ export default function createApp() {
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
app.use(insideSession);
|
app.use(insideSession);
|
||||||
|
createRoutes(app);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
src/server/routes/create.js
Normal file
5
src/server/routes/create.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import express from 'express';
|
||||||
|
|
||||||
|
export default function createRoutes(app) {
|
||||||
|
app.use('/resources', express.static('resources'));
|
||||||
|
}
|
25
yarn.lock
25
yarn.lock
|
@ -989,6 +989,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
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":
|
"@babel/template@^7.10.1":
|
||||||
version "7.10.1"
|
version "7.10.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/@babel%2ftemplate/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811"
|
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"
|
query-string "^4.1.0"
|
||||||
sort-keys "^1.0.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:
|
notepack.io@~2.1.2:
|
||||||
version "2.1.3"
|
version "2.1.3"
|
||||||
resolved "https://npm.i12e.cha0s.io/notepack.io/-/notepack.io-2.1.3.tgz#cc904045c751b1a27b2dcfd838d81d0bf3ced923"
|
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"
|
shallowequal "^1.1.0"
|
||||||
source-map "^0.7.3"
|
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"
|
version "16.13.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://npm.i12e.cha0s.io/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
@ -7625,6 +7637,17 @@ react-markdown@^4.3.1:
|
||||||
unist-util-visit "^1.3.0"
|
unist-util-visit "^1.3.0"
|
||||||
xtend "^4.0.1"
|
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:
|
react-tabs@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://npm.i12e.cha0s.io/react-tabs/-/react-tabs-3.1.1.tgz#b363a239f76046bb2158875a1e5921b11064052f"
|
resolved "https://npm.i12e.cha0s.io/react-tabs/-/react-tabs-3.1.1.tgz#b363a239f76046bb2158875a1e5921b11064052f"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user