mirror of
https://github.com/strapi/strapi.git
synced 2025-11-01 18:33:55 +00:00
Load and display models in left menu
This commit is contained in:
parent
ef7ad188f2
commit
e13c612509
@ -7,6 +7,13 @@
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
}, {
|
||||
"method": "GET",
|
||||
"path": "/api/models",
|
||||
"handler": "Admin.models",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
|
||||
@ -18,6 +18,10 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
models: async (ctx) => {
|
||||
ctx.body = strapi.models;
|
||||
},
|
||||
|
||||
file: async ctx => {
|
||||
try {
|
||||
const file = fs.readFileSync(path.resolve(__dirname, '..', 'public', 'build', ctx.params.file));
|
||||
|
||||
@ -103,7 +103,7 @@ window.onload = function onLoad() {
|
||||
// import { install } from 'offline-plugin/runtime';
|
||||
// install();
|
||||
|
||||
import { pluginLoaded } from './containers/App/actions';
|
||||
import { pluginLoaded, updatePlugin } from './containers/App/actions';
|
||||
|
||||
/**
|
||||
* Public Strapi object exposed to the `window` object
|
||||
@ -172,10 +172,13 @@ window.Strapi = {
|
||||
},
|
||||
port,
|
||||
apiUrl,
|
||||
refresh: () => ({
|
||||
refresh: (pluginId) => ({
|
||||
translationMessages: (translationMessagesUpdated) => {
|
||||
render(_.merge({}, translationMessages, translationMessagesUpdated));
|
||||
},
|
||||
leftMenuLinks: (leftMenuLinksUpdated) => {
|
||||
store.dispatch(updatePlugin(pluginId, 'leftMenuLinks', leftMenuLinksUpdated));
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
/**
|
||||
*
|
||||
* LeftMenuLink
|
||||
*
|
||||
*/
|
||||
*
|
||||
* LeftMenuLink
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styles from './styles.scss';
|
||||
@ -12,8 +12,13 @@ import LeftMenuSubLinkContainer from 'components/LeftMenuSubLinkContainer';
|
||||
class LeftMenuLink extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
let subLinksContainer;
|
||||
if (this.props.leftMenuLinks && this.props.leftMenuLinks.length) {
|
||||
subLinksContainer = <LeftMenuSubLinkContainer subLinks={this.props.leftMenuLinks} />;
|
||||
if (this.props.leftMenuLinks && this.props.leftMenuLinks.size) {
|
||||
subLinksContainer = (
|
||||
<LeftMenuSubLinkContainer
|
||||
subLinks={this.props.leftMenuLinks}
|
||||
destinationPrefix={this.props.destination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -33,7 +38,7 @@ LeftMenuLink.propTypes = {
|
||||
label: React.PropTypes.string,
|
||||
destination: React.PropTypes.string,
|
||||
isActive: React.PropTypes.bool,
|
||||
leftMenuLinks: React.PropTypes.array,
|
||||
leftMenuLinks: React.PropTypes.object,
|
||||
};
|
||||
|
||||
export default LeftMenuLink;
|
||||
|
||||
@ -11,14 +11,14 @@ import styles from './styles.scss';
|
||||
class LeftMenuLinkContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
// List of links
|
||||
let links = this.props.plugins.valueSeq().map(plugin => (
|
||||
let links = this.props.plugins.valueSeq().map((plugin) => (
|
||||
<LeftMenuLink
|
||||
key={plugin.id}
|
||||
icon={plugin.icon || 'ion-merge'}
|
||||
label={plugin.name}
|
||||
destination={`/plugins/${plugin.id}`}
|
||||
isActive={this.props.params.plugin === plugin.id}
|
||||
leftMenuLinks={plugin.leftMenuLinks}
|
||||
key={plugin.get('id')}
|
||||
icon={plugin.get('icon') || 'ion-merge'}
|
||||
label={plugin.get('name')}
|
||||
destination={`/plugins/${plugin.get('id')}`}
|
||||
isActive={this.props.params.plugin === plugin.get('id')}
|
||||
leftMenuLinks={plugin.get('leftMenuLinks')}
|
||||
/>
|
||||
));
|
||||
|
||||
|
||||
@ -14,8 +14,8 @@ class LeftMenuSubLinkContainer extends React.Component { // eslint-disable-line
|
||||
let links = this.props.subLinks.map((subLink, i) => (
|
||||
<LeftMenuSubLink
|
||||
key={i}
|
||||
label={subLink.label}
|
||||
destination={`/plugins/${subLink.to}`}
|
||||
label={subLink.get('label')}
|
||||
destination={`${this.props.destinationPrefix}/${subLink.get('to')}`}
|
||||
isActive={false}
|
||||
/>
|
||||
));
|
||||
@ -29,7 +29,8 @@ class LeftMenuSubLinkContainer extends React.Component { // eslint-disable-line
|
||||
}
|
||||
|
||||
LeftMenuSubLinkContainer.propTypes = {
|
||||
subLinks: React.PropTypes.array,
|
||||
subLinks: React.PropTypes.object,
|
||||
destinationPrefix: React.PropTypes.string,
|
||||
};
|
||||
|
||||
export default LeftMenuSubLinkContainer;
|
||||
|
||||
@ -5,8 +5,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
PLUGIN_LOADED,
|
||||
LOAD_PLUGIN,
|
||||
UPDATE_PLUGIN,
|
||||
PLUGIN_LOADED,
|
||||
} from './constants';
|
||||
|
||||
export function loadPlugin(newPlugin) {
|
||||
@ -16,6 +17,15 @@ export function loadPlugin(newPlugin) {
|
||||
};
|
||||
}
|
||||
|
||||
export function updatePlugin(pluginId, updatedKey, updatedValue) {
|
||||
return {
|
||||
type: UPDATE_PLUGIN,
|
||||
pluginId,
|
||||
updatedKey,
|
||||
updatedValue,
|
||||
};
|
||||
}
|
||||
|
||||
export function pluginLoaded(newPlugin) {
|
||||
return {
|
||||
type: PLUGIN_LOADED,
|
||||
|
||||
@ -5,4 +5,5 @@
|
||||
*/
|
||||
|
||||
export const LOAD_PLUGIN = 'app/App/LOAD_PLUGIN';
|
||||
export const UPDATE_PLUGIN = 'app/App/UPDATE_PLUGIN';
|
||||
export const PLUGIN_LOADED = 'app/App/PLUGIN_LOADED';
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import {
|
||||
UPDATE_PLUGIN,
|
||||
PLUGIN_LOADED,
|
||||
} from './constants';
|
||||
|
||||
@ -10,7 +11,9 @@ const initialState = fromJS({
|
||||
function appReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case PLUGIN_LOADED:
|
||||
return state.setIn(['plugins', action.plugin.id], action.plugin);
|
||||
return state.setIn(['plugins', action.plugin.id], fromJS(action.plugin));
|
||||
case UPDATE_PLUGIN:
|
||||
return state.setIn(['plugins', action.pluginId, action.updatedKey], fromJS(action.updatedValue));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export class PluginPage extends React.Component { // eslint-disable-line react/p
|
||||
|
||||
render() {
|
||||
const containers = this.props.plugins.valueSeq().map((plugin, i) => {
|
||||
const Elem = plugin.mainComponent;
|
||||
const Elem = plugin.get('mainComponent');
|
||||
return <Elem key={i} {...this.props} exposedComponents={exposedComponents}></Elem>;
|
||||
});
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
|
||||
import { browserHistory } from 'react-router';
|
||||
import configureStore from './store';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
// Create redux store with history
|
||||
// this uses the singleton browserHistory provided by react-router
|
||||
@ -23,20 +25,24 @@ import { translationMessages } from './i18n';
|
||||
// Plugin identifier based on the package.json `name` value
|
||||
const pluginId = require('../package.json').name.replace(/^strapi-plugin-/i, '');
|
||||
|
||||
class comp extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register the plugin
|
||||
if (window.Strapi) {
|
||||
window.Strapi.registerPlugin({
|
||||
name: 'Content Manager',
|
||||
icon: 'ion-document-text',
|
||||
id: pluginId,
|
||||
leftMenuLinks: [{
|
||||
label: 'Articles',
|
||||
to: 'content-manager/list/articles',
|
||||
}, {
|
||||
label: 'Categories',
|
||||
to: 'content-manager/list/categories',
|
||||
}],
|
||||
mainComponent: App,
|
||||
leftMenuLinks: [],
|
||||
mainComponent: comp,
|
||||
routes: createRoutes(store),
|
||||
translationMessages,
|
||||
});
|
||||
@ -64,4 +70,5 @@ const apiUrl = window.Strapi && `${window.Strapi.apiUrl}/${pluginId}`;
|
||||
export {
|
||||
store,
|
||||
apiUrl,
|
||||
pluginId,
|
||||
};
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
*
|
||||
* App actions
|
||||
*
|
||||
*/
|
||||
|
||||
import {
|
||||
LOAD_MODELS,
|
||||
LOADED_MODELS
|
||||
} from './constants';
|
||||
|
||||
export function loadModels() {
|
||||
return {
|
||||
type: LOAD_MODELS,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadedModels(models) {
|
||||
return {
|
||||
type: LOADED_MODELS,
|
||||
models
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
*
|
||||
* App constants
|
||||
*
|
||||
*/
|
||||
|
||||
export const LOAD_MODELS = 'contentManager/App/LOAD_MODELS';
|
||||
export const LOADED_MODELS = 'contentManager/App/LOADED_MODELS';
|
||||
@ -6,12 +6,18 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from '../../app';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { loadModels } from './actions';
|
||||
import { makeSelectModels } from './selectors';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import '../../styles/main.scss';
|
||||
|
||||
export default class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
class App extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
componentWillMount() {
|
||||
this.props.loadModels();
|
||||
}
|
||||
|
||||
render() {
|
||||
// Assign plugin component to children
|
||||
const childrenWithProps = React.Children.map(this.props.children,
|
||||
@ -21,11 +27,28 @@ export default class App extends React.Component { // eslint-disable-line react/
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<div className='content-manager'>
|
||||
{React.Children.toArray(childrenWithProps)}
|
||||
</div>
|
||||
</Provider>
|
||||
<div className='content-manager'>
|
||||
{React.Children.toArray(childrenWithProps)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
App.propTypes = {
|
||||
children: React.PropTypes.node,
|
||||
loadModels: React.PropTypes.func,
|
||||
};
|
||||
|
||||
export function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
loadModels: () => dispatch(loadModels()),
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
models: makeSelectModels(),
|
||||
});
|
||||
|
||||
// Wrap the component to inject dispatch and state into it
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
/*
|
||||
*
|
||||
* List reducer
|
||||
*
|
||||
*/
|
||||
|
||||
import { fromJS } from 'immutable';
|
||||
import {
|
||||
LOAD_MODELS,
|
||||
LOADED_MODELS,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
loading: false,
|
||||
models: {}
|
||||
});
|
||||
|
||||
function appReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case LOAD_MODELS:
|
||||
return state
|
||||
.set('loading', true);
|
||||
case LOADED_MODELS:
|
||||
return state
|
||||
.set('loading', false)
|
||||
.set('models', action.models);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default appReducer;
|
||||
@ -0,0 +1,46 @@
|
||||
import { takeLatest } from 'redux-saga';
|
||||
import { fork, put } from 'redux-saga/effects';
|
||||
import _ from 'lodash';
|
||||
|
||||
import {
|
||||
loadedModels
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
LOAD_MODELS,
|
||||
} from './constants';
|
||||
|
||||
export function* getModels() {
|
||||
try {
|
||||
const opts = {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
cache: 'default'
|
||||
};
|
||||
const response = yield fetch('http://localhost:1337/admin/api/models', opts);
|
||||
const data = yield response.json();
|
||||
|
||||
yield put(loadedModels(data));
|
||||
|
||||
const leftMenuLinks = _.map(data, (model, key) => ({
|
||||
label: model.globalId,
|
||||
to: key,
|
||||
}));
|
||||
|
||||
// Update the admin left menu links
|
||||
window.Strapi.refresh('content-manager').leftMenuLinks(leftMenuLinks);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Individual exports for testing
|
||||
export function* defaultSaga() {
|
||||
// yield takeLatest(LOAD_MODELS, getModels);
|
||||
yield fork(takeLatest, LOAD_MODELS, getModels);
|
||||
}
|
||||
|
||||
// All sagas to be loaded
|
||||
export default [
|
||||
defaultSaga,
|
||||
];
|
||||
@ -0,0 +1,31 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
/**
|
||||
* Direct selector to the list state domain
|
||||
*/
|
||||
const selectGlobalDomain = () => (state) => state.get('global');
|
||||
|
||||
/**
|
||||
* Other specific selectors
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Default selector used by List
|
||||
*/
|
||||
|
||||
const makeSelectModels = () => createSelector(
|
||||
selectGlobalDomain(),
|
||||
(globalState) => globalState.get('models'),
|
||||
);
|
||||
|
||||
const makeSelectLoading = () => createSelector(
|
||||
selectGlobalDomain(),
|
||||
(substate) => substate.get('loading')
|
||||
);
|
||||
|
||||
export {
|
||||
selectGlobalDomain,
|
||||
makeSelectLoading,
|
||||
makeSelectModels,
|
||||
};
|
||||
@ -19,7 +19,7 @@ import {
|
||||
|
||||
import {
|
||||
makeSelectModelRecords,
|
||||
makeSelectLoading
|
||||
makeSelectLoading,
|
||||
} from './selectors';
|
||||
|
||||
export class List extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
@ -84,7 +84,7 @@ function mapDispatchToProps(dispatch) {
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
records: makeSelectModelRecords(),
|
||||
loading: makeSelectLoading()
|
||||
loading: makeSelectLoading(),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(List));
|
||||
|
||||
@ -30,7 +30,7 @@ export function* getRecords() {
|
||||
mode: 'cors',
|
||||
cache: 'default'
|
||||
};
|
||||
const response = yield fetch('http://localhost:1337/admin/config/models', opts);
|
||||
const response = yield fetch('http://localhost:1337/admin/api/models', opts);
|
||||
const data = yield response.json();
|
||||
|
||||
yield put(loadedRecord(fakeData, data));
|
||||
|
||||
@ -7,6 +7,8 @@ import { combineReducers } from 'redux-immutable';
|
||||
import { fromJS } from 'immutable';
|
||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
||||
|
||||
import globalReducer from 'containers/App/reducer';
|
||||
|
||||
/*
|
||||
* routeReducer
|
||||
*
|
||||
@ -41,6 +43,7 @@ function routeReducer(state = routeInitialState, action) {
|
||||
export default function createReducer(asyncReducers) {
|
||||
return combineReducers({
|
||||
route: routeReducer,
|
||||
global: globalReducer,
|
||||
...asyncReducers,
|
||||
});
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// See http://blog.mxstbr.com/2016/01/react-apps-with-pages for more information
|
||||
// about the code splitting business
|
||||
import { getAsyncInjectors } from 'utils/asyncInjectors';
|
||||
import appSagas from 'containers/App/sagas';
|
||||
|
||||
const loadModule = (cb) => (componentModule) => {
|
||||
cb(null, componentModule.default);
|
||||
@ -12,6 +13,9 @@ export default function createRoutes(store) {
|
||||
// Create reusable async injectors using getAsyncInjectors factory
|
||||
const { injectReducer, injectSagas } = getAsyncInjectors(store); // eslint-disable-line no-unused-vars
|
||||
|
||||
// Inject app sagas
|
||||
injectSagas(appSagas);
|
||||
|
||||
return [
|
||||
{
|
||||
path: '',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user