diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..13cfd2b3a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Jest Current File", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": ["${relativeFile}"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + } + } + ], + "compounds": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 834d2d81a..6a51dc890 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "typescript.tsdk": "node_modules\\typescript\\lib", - "tslint.autoFixOnSave": true, - "editor.formatOnSave": true + "typescript.tsdk": "node_modules\\typescript\\lib", + "editor.formatOnSave": true } diff --git a/examples/sn-dms-demo/README.md b/examples/sn-dms-demo/README.md index b37262da9..2e155f324 100644 --- a/examples/sn-dms-demo/README.md +++ b/examples/sn-dms-demo/README.md @@ -1,35 +1,15 @@ # sn-dms-demo -[![Join the chat at https://gitter.im/SenseNet/sn-dms-demo](https://badges.gitter.im/SenseNet/sn-dms-demo.svg)](https://gitter.im/SenseNet/sn-dms-demo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/SenseNet/sn-dms-demo.svg?branch=master)](https://travis-ci.org/SenseNet/sn-dms-demo) -[![codecov](https://codecov.io/gh/SenseNet/sn-dms-demo/branch/master/graph/badge.svg)](https://codecov.io/gh/SenseNet/sn-dms-demo) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/708a03362ad447958a6830badfc61d80)](https://www.codacy.com/app/herflis33/sn-dms-demo?utm_source=github.com&utm_medium=referral&utm_content=SenseNet/sn-dms-demo&utm_campaign=Badge_Grade) -[![License](https://img.shields.io/github/license/SenseNet/sn-dms-demo.svg?style=flat)](https://github.com/SenseNet/sn-dms-demo/LICENSE.txt) -[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat)](https://github.com/semantic-release/semantic-release) -[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat)](http://commitizen.github.io/cz-cli/) -[![Greenkeeper badge](https://badges.greenkeeper.io/SenseNet/sn-dms-demo.svg)](https://greenkeeper.io/) - -sensenet Document Management demo with React. - -[![Sense/Net Services](https://img.shields.io/badge/sensenet-7.0.0--beta3%20tested-green.svg)](https://github.com/SenseNet/sensenet/releases/tag/v7.0.0-beta3) +A sensenet Document Management demo with React. ## Install and start -``` -$ git clone https://github.com/SenseNet/sn-dms-demo.git -$ cd sn-dms-demo -$ npm install -$ npm start -``` - -Please set the following enviroment variables: -- REACT_APP_SERVICE_URL: url of the site that has at least [sensenet services](https://github.com/SenseNet/sensenet) installed (default value is ```https://sn-local``` so if you have a site installed locally with this url, you have nothing to do with this). -- REACT_APP_RECAPTCHA_KEY: [Google ReCAPTCHA](https://www.google.com/recaptcha/intro/) sitekey. Without this registration won't work. - -## Running Tests - -``` -$ npm test -``` -The app is built with [create-react-app-typescript](https://github.com/wmonk/create-react-app-typescript) the TypeScript version of [create-react-app](https://github.com/facebookincubator/create-react-app), so for further information about build, test, config, etc. issues, check them on github. \ No newline at end of file +1. Install yarn globally with: **npm i yarn -g** +2. Clone the monorepo with: **git clone https://github.com/SenseNet/sn-client.git** +3. Navigate into the monorepo's root folder: **cd sn-client** +4. Install all of the dependencies with a simple **yarn** command (this will take a second or two) +5. Set the environment options, Repository path in index.tsx, etc... *(Since this app is for demo purposes it requires several demo content items (e.g. demo users, demo content types, demo templates, demo actions) that are NOT the part of the official sensenet packages. So please, please if you want to try out the examples of the sn-client package, do it with connecting to our fully prepared demo sensenet instance at [https://dmsservice.demo.sensenet.com](https://dmsservice.demo.sensenet.com). Note: we rebuild this service daily and sometimes more frequently. When a reset happens, expect short downtimes and losing any changes you made before.)* +6. Build all of the packages with the command **yarn build** +7. Run the application with **yarn workspace sn-dms-demo run start** +8. You should be able to login at https://localhost:3000 diff --git a/examples/sn-dms-demo/package.json b/examples/sn-dms-demo/package.json index 25c0b3484..57069ce81 100644 --- a/examples/sn-dms-demo/package.json +++ b/examples/sn-dms-demo/package.json @@ -1,6 +1,6 @@ { "name": "sn-dms-demo", - "version": "1.1.0", + "version": "1.2.0", "description": "Document Management Demo upon sensenet", "private": true, "repository": { @@ -27,51 +27,49 @@ } }, "dependencies": { - "@date-io/date-fns": "0.0.2", - "@date-io/moment": "0.0.2", "@material-ui/core": "^3.8.2", "@material-ui/icons": "^3.0.2", - "@sensenet/authentication-google": "^2.0.3", - "@sensenet/authentication-jwt": "^1.0.7", - "@sensenet/client-core": "^1.4.0", + "@sensenet/authentication-google": "^2.0.4", + "@sensenet/authentication-jwt": "^1.0.8", + "@sensenet/client-core": "^1.4.1", "@sensenet/client-utils": "^1.4.2", - "@sensenet/controls-react": "^2.3.7", + "@sensenet/controls-react": "^2.4.0", "@sensenet/default-content-types": "^1.1.2", - "@sensenet/document-viewer-react": "^1.0.4", - "@sensenet/icons-react": "^1.2.5", - "@sensenet/list-controls-react": "^1.3.2", - "@sensenet/redux": "^5.1.4", - "@sensenet/repository-events": "^1.3.0", - "@sensenet/search-react": "^1.2.0", + "@sensenet/document-viewer-react": "^1.0.5", + "@sensenet/icons-react": "^1.2.6", + "@sensenet/list-controls-react": "^1.3.3", + "@sensenet/pickers-react": "^1.0.0", + "@sensenet/redux": "^5.1.5", + "@sensenet/repository-events": "^1.3.1", + "@sensenet/search-react": "^1.2.1", "autoprefixer": "^9.4.2", "css-loader": "^2.1.0", - "date-fns": "^2.0.0-alpha.21", "dotenv": "6.2.0", "file-loader": "3.0.1", "fs-extra": "^7.0.1", "history": "^4.7.2", "html-webpack-plugin": "^3.2.0", "lodash.groupby": "^4.6.0", - "material-ui-pickers": "^2.0.4", "moment": "^2.22.2", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "4.1.0", "postcss-loader": "^3.0.0", "promise": "8.0.2", - "react": "^16.6.3", + "react": "^16.8.1", "react-async-script": "^1.0.0", "react-cookie-consent": "^2.0.1", "react-custom-scrollbars": "^4.2.1", - "react-dom": "^16.6.3", + "react-dom": "^16.8.1", "react-google-recaptcha": "^1.0.5", "react-loadable": "^5.5.0", "react-markdown": "^4.0.4", "react-moment": "^0.8.4", "react-redux": "^6.0.0", "react-responsive": "^6.0.1", + "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "redux": "^4.0.1", - "redux-di-middleware": "^2.0.2", + "redux-di-middleware": "^3.0.0", "reflect-metadata": "^0.1.12", "ts-keycode-enum": "^1.0.6", "typeface-roboto": "^0.0.54", diff --git a/examples/sn-dms-demo/src/Actions.ts b/examples/sn-dms-demo/src/Actions.ts index 2bbb69daa..4af08a0dc 100644 --- a/examples/sn-dms-demo/src/Actions.ts +++ b/examples/sn-dms-demo/src/Actions.ts @@ -8,8 +8,8 @@ import { import { debounce, ObservableValue, usingAsync } from '@sensenet/client-utils' import { File as SnFile, GenericContent } from '@sensenet/default-content-types' import { ActionModel } from '@sensenet/default-content-types/dist/ActionModel' -import { Action, AnyAction, Dispatch } from 'redux' -import { InjectableAction } from 'redux-di-middleware' +import { Dispatch } from 'redux' +import { IInjectableActionCallbackParams } from 'redux-di-middleware' import { updateChildrenOptions } from './store/documentlibrary/actions' import { rootStateType } from './store/rootReducer' @@ -78,25 +78,24 @@ export const setEditedFirst = (edited: boolean) => ({ edited, }) -export const getListActions = (idOrPath: number | string, scenario?: string, customActions?: ActionModel[]) => - ({ - type: 'GET_LIST_ACTIONS', - async inject(options) { - const actionsState = options.getState().dms.toolbar - if (!actionsState.isLoading && (actionsState.idOrPath !== idOrPath || actionsState.scenario !== scenario)) { - options.dispatch(loadListActions(idOrPath, scenario)) - const repository = options.getInjectable(Repository) - const data: { d: { Actions: ActionModel[] } } = (await repository.getActions({ idOrPath, scenario })) as any - const actions = customActions ? [...data.d.Actions, ...customActions] : data.d.Actions - const ordered = actions.sort((a, b) => { - const x = a.Index - const y = b.Index - return x < y ? -1 : x > y ? 1 : 0 - }) - options.dispatch(setListActions(ordered)) - } - }, - } as InjectableAction) +export const getListActions = (idOrPath: number | string, scenario?: string, customActions?: ActionModel[]) => ({ + type: 'GET_LIST_ACTIONS', + async inject(options: IInjectableActionCallbackParams) { + const actionsState = options.getState().dms.toolbar + if (!actionsState.isLoading && (actionsState.idOrPath !== idOrPath || actionsState.scenario !== scenario)) { + options.dispatch(loadListActions(idOrPath, scenario)) + const repository = options.getInjectable(Repository) + const data: { d: { Actions: ActionModel[] } } = (await repository.getActions({ idOrPath, scenario })) as any + const actions = customActions ? [...data.d.Actions, ...customActions] : data.d.Actions + const ordered = actions.sort((a, b) => { + const x = a.Index + const y = b.Index + return x < y ? -1 : x > y ? 1 : 0 + }) + options.dispatch(setListActions(ordered)) + } + }, +}) export const loadListActions = (idOrPath: number | string, scenario?: string) => ({ type: 'LOAD_LIST_ACTIONS', @@ -147,9 +146,7 @@ export const trackUploadProgress = async ( dispatch: Dispatch, api: Repository, ) => { - let currentUpload: ExtendedUploadProgressInfo | undefined = getState().dms.uploads.uploads.find( - u => u.guid === currentValue.guid, - ) + let currentUpload = getState().dms.uploads.uploads.find(u => u.guid === currentValue.guid) if (currentUpload) { dispatch(updateUploadItem(currentValue)) } else { @@ -175,13 +172,11 @@ export const trackUploadProgress = async ( } } -export const uploadFileList: ( - uploadOptions: Pick, Exclude, 'repository'>>, -) => InjectableAction = ( +export const uploadFileList = ( uploadOptions: Pick, Exclude, 'repository'>>, ) => ({ type: 'DMS_UPLOAD_FILE_LIST_INJECTABLE_ACTION', - inject: async options => { + inject: async (options: IInjectableActionCallbackParams) => { const api = options.getInjectable(Repository) await usingAsync(new ObservableValue(), async progress => { progress.subscribe(async currentValue => @@ -203,13 +198,11 @@ export const uploadFileList: ( }, }) -export const uploadDataTransfer: ( - options: Pick, Exclude, 'repository'>>, -) => InjectableAction = ( +export const uploadDataTransfer = ( uploadOptions: Pick, Exclude, 'repository'>>, ) => ({ type: 'DMS_UPLOAD_DATA_TRANSFER_INJECTABLE_ACTION', - inject: async options => { + inject: async (options: IInjectableActionCallbackParams) => { const api = options.getInjectable(Repository) await usingAsync(new ObservableValue(), async progress => { progress.subscribe(async currentValue => @@ -328,21 +321,20 @@ export const handleDrawerMenu = (open: boolean) => ({ open, }) -export const loadBreadcrumbActions = (idOrPath: number | string) => - ({ - type: 'LOAD_BREADCRUMB_ACTIONS', - inject: async options => { - if (idOrPath === options.getState().dms.actionmenu.breadcrumb.idOrPath) { - return - } - const repository = options.getInjectable(Repository) - const actions: { d: { Actions: ActionModel[] } } = (await repository.getActions({ - idOrPath, - scenario: 'DMSBreadcrumb', - })) as any - options.dispatch({ - type: 'LOAD_BREADCRUMB_ACTIONS_SUCCESS', - result: { idOrPath, actions: actions.d.Actions }, - }) - }, - } as InjectableAction) +export const loadBreadcrumbActions = (idOrPath: number | string) => ({ + type: 'LOAD_BREADCRUMB_ACTIONS', + inject: async (options: IInjectableActionCallbackParams) => { + if (idOrPath === options.getState().dms.actionmenu.breadcrumb.idOrPath) { + return + } + const repository = options.getInjectable(Repository) + const actions: { d: { Actions: ActionModel[] } } = (await repository.getActions({ + idOrPath, + scenario: 'DMSBreadcrumb', + })) as any + options.dispatch({ + type: 'LOAD_BREADCRUMB_ACTIONS_SUCCESS', + result: { idOrPath, actions: actions.d.Actions }, + }) + }, +}) diff --git a/examples/sn-dms-demo/src/Reducers.ts b/examples/sn-dms-demo/src/Reducers.ts index fd3ded124..1969306ee 100644 --- a/examples/sn-dms-demo/src/Reducers.ts +++ b/examples/sn-dms-demo/src/Reducers.ts @@ -29,7 +29,7 @@ export const email: Reducer = (state = '', return state } } -export const registrationError: Reducer = (state: string | null = null, action: AnyAction) => { +export const registrationError: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'USER_REGISTRATION_FAILURE': return resources.USER_IS_ALREADY_REGISTERED @@ -37,7 +37,7 @@ export const registrationError: Reducer = (state: string return state } } -export const isRegistering: Reducer = (state: boolean = false, action: AnyAction) => { +export const isRegistering: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'USER_REGISTRATION_REQUEST': return true @@ -49,7 +49,7 @@ export const isRegistering: Reducer = (state: boolean = false, action: } } -export const registrationDone: Reducer = (state: boolean = false, action: AnyAction) => { +export const registrationDone: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'USER_REGISTRATION_SUCCESS': return true @@ -62,7 +62,7 @@ export const registrationDone: Reducer = (state: boolean = false, actio } } -export const captcha: Reducer = (state: boolean = false, action: AnyAction) => { +export const captcha: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'VERIFY_CAPTCHA_SUCCESS': return true @@ -79,7 +79,7 @@ export const register = combineReducers({ captcha, }) -export const open: Reducer = (state: boolean = false, action: AnyAction) => { +export const open: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'OPEN_ACTIONMENU': return true @@ -90,7 +90,7 @@ export const open: Reducer = (state: boolean = false, action: AnyAction } } -export const actions: Reducer = (state: ActionModel[] = [], action: AnyAction) => { +export const actions: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'LOAD_CONTENT_ACTIONS_SUCCESS': const result: { d: { Actions: ActionModel[] } } = (action.result as Actions.PromiseReturns< @@ -104,7 +104,7 @@ export const actions: Reducer = (state: ActionModel[] = [], actio } } -export const id: Reducer = (state: number | null = null, action: AnyAction) => { +export const id: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'OPEN_ACTIONMENU': return action.id || null @@ -248,7 +248,7 @@ export const isSelectionModeOn: Reducer = (state = false, action: AnyAc } } -export const userActions: Reducer = (state: ActionModel[] = [], action: AnyAction) => { +export const userActions: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'LOAD_USER_ACTIONS_SUCCESS': const result = action.result as Actions.PromiseReturns @@ -258,7 +258,7 @@ export const userActions: Reducer = (state: ActionModel[] = [], a } } -export const addNewTypes: Reducer = (state: ActionModel[] = [], action: AnyAction) => { +export const addNewTypes: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'LOAD_TYPES_TO_ADDNEW_LIST_SUCCESS': const result = action.result as Actions.PromiseReturns @@ -268,7 +268,7 @@ export const addNewTypes: Reducer = (state: ActionModel[] = [], a } } -export const actionmenuContent: Reducer = (state: GenericContent | null = null, action: AnyAction) => { +export const actionmenuContent: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'OPEN_ACTIONMENU': return action.content @@ -435,7 +435,7 @@ export const viewer: Reducer<{ isOpened: boolean; currentDocumentId: number }, A return state } -export const isOpened: Reducer = (state: boolean = false, action: AnyAction) => { +export const isOpened: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'OPEN_DIALOG': return true @@ -445,10 +445,7 @@ export const isOpened: Reducer = (state: boolean = false, action: AnyAc return state } -export const onClose: Reducer<() => void | undefined> = ( - state: () => void | undefined = () => undefined, - action: AnyAction, -) => { +export const onClose: Reducer<() => void | undefined> = (state = () => undefined, action: AnyAction) => { switch (action.type) { case 'OPEN_DIALOG': return action.onClose @@ -471,7 +468,7 @@ export const dialogContent: Reducer = ( return state } -export const dialogTitle: Reducer = (state: string = '', action: AnyAction) => { +export const dialogTitle: Reducer = (state = '', action: AnyAction) => { switch (action.type) { case 'OPEN_DIALOG': return action.title @@ -498,7 +495,7 @@ export const versions = (state: GenericContent[] = [], action: AnyAction) => { } } -export const menuOpen: Reducer = (state: boolean = false, action: AnyAction) => { +export const menuOpen: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'HANDLE_DRAWERMENU': return action.open diff --git a/examples/sn-dms-demo/src/ViewerSettings.ts b/examples/sn-dms-demo/src/ViewerSettings.ts index 0cf739b1f..e195b9754 100644 --- a/examples/sn-dms-demo/src/ViewerSettings.ts +++ b/examples/sn-dms-demo/src/ViewerSettings.ts @@ -14,12 +14,12 @@ import { v1 } from 'uuid' /** * Adds a globally unique ID to the shape */ -const addGuidToShape: (shape: T) => T = shape => { +const addGuidToShape = (shape: T) => { shape.guid = v1() return shape } -export const getViewerSettings: (repo: Repository) => DocumentViewerSettings = (repo: Repository) => +export const getViewerSettings = (repo: Repository) => new DocumentViewerSettings({ saveChanges: async (documentData, pages) => { const reqBody = { @@ -67,7 +67,7 @@ export const getViewerSettings: (repo: Repository) => DocumentViewerSettings = ( pageAttributes: (documentData.PageAttributes && JSON.parse(documentData.PageAttributes)) || [], } }, - isPreviewAvailable: async (documentData, version, page: number) => { + isPreviewAvailable: async (documentData, version, page) => { const responseBody = await repo.executeAction<{ page: number }, PreviewImageData & { PreviewAvailable: string }>({ idOrPath: documentData.idOrPath, method: 'POST', diff --git a/examples/sn-dms-demo/src/assets/helpers.ts b/examples/sn-dms-demo/src/assets/helpers.ts index fe46689f9..ad1aa01c0 100644 --- a/examples/sn-dms-demo/src/assets/helpers.ts +++ b/examples/sn-dms-demo/src/assets/helpers.ts @@ -1,3 +1,5 @@ +import { Group } from '@sensenet/default-content-types' + export const getContentTypeFromUrl = (urlString: string) => { const urlTemp = urlString.split('ContentTypeName=')[1] const type = urlTemp.indexOf('&') > -1 ? urlTemp.split('&')[0] : urlTemp @@ -46,3 +48,6 @@ export const arrayComparer = (array1: any[], array2: any[]) => const uniqueResultOne = (array1: any[], array2: any[]) => array1.filter(obj => !array2.some(obj2 => obj.Id === obj2.Id)) const uniqueResultTwo = (array1: any[], array2: any[]) => array2.filter(obj => !array1.some(obj2 => obj.Id === obj2.Id)) + +export const arrayDiff = (a1: Group[], a2: Group[]) => + a1.filter(item1 => !a2.some(item2 => item2.Id === item1.Id && item2.Name === item1.Name)) diff --git a/examples/sn-dms-demo/src/assets/picker.ts b/examples/sn-dms-demo/src/assets/picker.ts index b84afefe6..c0f193944 100644 --- a/examples/sn-dms-demo/src/assets/picker.ts +++ b/examples/sn-dms-demo/src/assets/picker.ts @@ -30,6 +30,9 @@ export const pickerTheme = createMuiTheme({ fontSize: 18, flex: 1, }, + h6: { + paddingLeft: theme.spacing.unit * 2, + }, subheading: { fontFamily: 'Raleway Medium', fontSize: 15, @@ -79,6 +82,19 @@ export const pickerTheme = createMuiTheme({ }, }, borderBottom: 'solid 1px #ddd', + '&$selected, &$selected:hover, &$selected:focus': { + '& path': { + fill: '#fff', + }, + '& span': { + color: '#fff', + }, + backgroundColor: '#016d9e', + color: '#fff', + }, + }, + button: { + '&:focus': { backgroundColor: 'unset' }, }, gutters: { padding: '12px !important', diff --git a/examples/sn-dms-demo/src/assets/resources.ts b/examples/sn-dms-demo/src/assets/resources.ts index 79dab408b..3be862304 100644 --- a/examples/sn-dms-demo/src/assets/resources.ts +++ b/examples/sn-dms-demo/src/assets/resources.ts @@ -135,8 +135,8 @@ const resources = { TO: 'to', COPY: 'Copy content', ARE_YOU_SURE_YOU_WANT_TO_COPY: 'You are about to copy the following content item(s):', - MOVETO_BUTTON: 'Move content here', - COPYTO_BUTTON: 'Copy content here', + MOVE_BUTTON: 'Move content here', + COPY_BUTTON: 'Copy content here', NEW_FOLDER: 'New folder', SEARCH_RESULTS: 'Search results', SEARCH_RESULTS_FOR: 'Search results for', diff --git a/examples/sn-dms-demo/src/assets/schema.ts b/examples/sn-dms-demo/src/assets/schema.ts index 89aea9f15..e4162a82a 100644 --- a/examples/sn-dms-demo/src/assets/schema.ts +++ b/examples/sn-dms-demo/src/assets/schema.ts @@ -8113,8 +8113,8 @@ export const customSchema = [ Compulsory: false, Visible: true, VisibleBrowse: 0, - VisibleEdit: 1, - VisibleNew: 1, + VisibleEdit: 0, + VisibleNew: 0, DefaultOrder: 0, ControlHint: 'sn:Image', }, diff --git a/examples/sn-dms-demo/src/components/ActionMenu/ActionMenu.tsx b/examples/sn-dms-demo/src/components/ActionMenu/ActionMenu.tsx index 7d6fee64f..ee4b73bce 100644 --- a/examples/sn-dms-demo/src/components/ActionMenu/ActionMenu.tsx +++ b/examples/sn-dms-demo/src/components/ActionMenu/ActionMenu.tsx @@ -161,6 +161,9 @@ class ActionMenu extends React.Component< ;(action as any).Action() } else { const content = this.props.currentContent + if (!content) { + return + } switch (action.Name) { case 'Rename': this.handleClose() @@ -172,9 +175,11 @@ class ActionMenu extends React.Component< break case 'DeleteBatch': case 'Delete': - this.handleClose() - this.props.clearSelection() - this.props.openDialog(, resources.DELETE, this.props.closeDialog) + if (content) { + this.handleClose() + this.props.clearSelection() + this.props.openDialog(, resources.DELETE, this.props.closeDialog) + } break case 'Preview': this.handleClose() @@ -254,12 +259,11 @@ class ActionMenu extends React.Component< break case 'MoveTo': this.handleClose() - this.props.select(content ? [content] : []) - this.props.setPickerParent(this.props.currentParent ? this.props.currentParent : null) - this.props.loadPickerItems(this.props.currentParent ? this.props.currentParent.Path : '') this.props.openPicker( } dialogTitle={resources.MOVE} dialogCallback={Actions.moveBatch as any} @@ -272,12 +276,11 @@ class ActionMenu extends React.Component< break case 'CopyTo': this.handleClose() - this.props.select(content ? [content] : []) - this.props.setPickerParent(this.props.currentParent ? this.props.currentParent : null) - this.props.loadPickerItems(this.props.currentParent ? this.props.currentParent.Path : '') this.props.openPicker( } dialogTitle={resources.COPY} dialogCallback={Actions.copyBatch as any} @@ -288,11 +291,11 @@ class ActionMenu extends React.Component< break case 'MoveBatch': this.handleClose() - this.props.setPickerParent(this.props.currentParent ? this.props.currentParent : null) - this.props.loadPickerItems(this.props.currentParent ? this.props.currentParent.Path : '') this.props.openPicker( } dialogTitle={resources.MOVE} dialogCallback={Actions.moveBatch as any} @@ -322,7 +325,6 @@ class ActionMenu extends React.Component< (await this.props.uploadFileList({ fileList: ev.target.files, createFolders: true, - contentTypeName: 'File', binaryPropertyName: 'Binary', overwrite: false, parentPath: this.props.currentParent ? this.props.currentParent.Path : '', diff --git a/examples/sn-dms-demo/src/components/ActionMenu/AddNewMenu.tsx b/examples/sn-dms-demo/src/components/ActionMenu/AddNewMenu.tsx index ac73566f9..98f9102d4 100644 --- a/examples/sn-dms-demo/src/components/ActionMenu/AddNewMenu.tsx +++ b/examples/sn-dms-demo/src/components/ActionMenu/AddNewMenu.tsx @@ -120,7 +120,7 @@ class AddNewMenu extends React.Component< ) } public render() { - return ) => this.handleButtonClick(e)} /> + return this.handleButtonClick(e)} /> } } diff --git a/examples/sn-dms-demo/src/components/BreadCrumb.tsx b/examples/sn-dms-demo/src/components/BreadCrumb.tsx index affccd84c..d0fbfbcb2 100644 --- a/examples/sn-dms-demo/src/components/BreadCrumb.tsx +++ b/examples/sn-dms-demo/src/components/BreadCrumb.tsx @@ -141,7 +141,7 @@ class BreadCrumb extends React.Component< ) : ( ) => this.handleActionMenuClick(e, ancestor)} + onClick={e => this.handleActionMenuClick(e, ancestor)} type={iconType.materialui} iconName={icons.arrowDropDown} /> @@ -150,7 +150,7 @@ class BreadCrumb extends React.Component< {!isLast ? ( ) => this.handleActionMenuClick(e, ancestor)} + onClick={e => this.handleActionMenuClick(e, ancestor)} type={iconType.materialui} iconName={icons.arrowRight} /> diff --git a/examples/sn-dms-demo/src/components/ContentList/CellTemplates/DisplayNameCell.tsx b/examples/sn-dms-demo/src/components/ContentList/CellTemplates/DisplayNameCell.tsx index a276e9d84..551b707fb 100644 --- a/examples/sn-dms-demo/src/components/ContentList/CellTemplates/DisplayNameCell.tsx +++ b/examples/sn-dms-demo/src/components/ContentList/CellTemplates/DisplayNameCell.tsx @@ -1,4 +1,4 @@ -import { TableCell } from '@material-ui/core' +import TableCell from '@material-ui/core/TableCell' import { GenericContent } from '@sensenet/default-content-types' import { Icon, iconType } from '@sensenet/icons-react' import * as React from 'react' diff --git a/examples/sn-dms-demo/src/components/Dialogs/DialogInfo.tsx b/examples/sn-dms-demo/src/components/Dialogs/DialogInfo.tsx index d73d561f3..6fee7fd42 100644 --- a/examples/sn-dms-demo/src/components/Dialogs/DialogInfo.tsx +++ b/examples/sn-dms-demo/src/components/Dialogs/DialogInfo.tsx @@ -96,7 +96,7 @@ class DialogInfo extends React.Component<{ classes: any } & DialogInfoProps, {}> currentContent && currentContent.Type === 'User' ? ( // tslint:disable-next-line:no-string-literal ) : ( diff --git a/examples/sn-dms-demo/src/components/Dialogs/EditPropertiesDialog.tsx b/examples/sn-dms-demo/src/components/Dialogs/EditPropertiesDialog.tsx index 5dccaf88b..52b3cbc82 100644 --- a/examples/sn-dms-demo/src/components/Dialogs/EditPropertiesDialog.tsx +++ b/examples/sn-dms-demo/src/components/Dialogs/EditPropertiesDialog.tsx @@ -26,6 +26,7 @@ const mapStateToProps = (state: rootStateType) => { editedcontent: state.dms.edited, items: state.dms.documentLibrary.items, repositoryUrl: state.sensenet.session.repository ? state.sensenet.session.repository.repositoryUrl : '', + currentUser: state.sensenet.session.user.userName, } } @@ -107,6 +108,8 @@ class EditPropertiesDialog extends React.Component< onSubmit={editContent} handleCancel={() => this.handleCancel()} submitCallback={this.submitCallback} + repositoryUrl={repositoryUrl} + uploadFolderPath={`/Root/Profiles/Public/${this.props.currentUser}/Document_Library`} /> ) : ( diff --git a/examples/sn-dms-demo/src/components/Dialogs/VersionsDialog.tsx b/examples/sn-dms-demo/src/components/Dialogs/VersionsDialog.tsx index 53e898744..55d991c31 100644 --- a/examples/sn-dms-demo/src/components/Dialogs/VersionsDialog.tsx +++ b/examples/sn-dms-demo/src/components/Dialogs/VersionsDialog.tsx @@ -240,7 +240,7 @@ class VersionsDialog extends React.Component< - {versions.map((version, index: number) => ( + {versions.map((version, index) => ( {this.formatVersionNumber(version.Version || '')} @@ -286,7 +286,7 @@ class VersionsDialog extends React.Component< ) : ( {resources.VERSIONS} - {versions.map((version, index: number) => ( + {versions.map((version, index) => ( { - this.props.updateChildrenOptions({ - ...this.props.childrenOptions, - orderby: [[field, direction]], - }) + if (field !== 'Workspace' && field !== 'Owner' && field !== 'Actions') { + this.props.updateChildrenOptions({ + ...this.props.childrenOptions, + orderby: [[field, direction]], + }) + } }} onItemClick={(ev, content) => { if (ev.ctrlKey) { diff --git a/examples/sn-dms-demo/src/components/LoginTabs.tsx b/examples/sn-dms-demo/src/components/LoginTabs.tsx index 8384d928f..2d9f850f0 100644 --- a/examples/sn-dms-demo/src/components/LoginTabs.tsx +++ b/examples/sn-dms-demo/src/components/LoginTabs.tsx @@ -3,7 +3,7 @@ import createStyles from '@material-ui/core/styles/createStyles' import withStyles from '@material-ui/core/styles/withStyles' import Tab from '@material-ui/core/Tab' import Tabs from '@material-ui/core/Tabs' -import * as React from 'react' +import React from 'react' import { RouteComponentProps, withRouter } from 'react-router-dom' const styles = { @@ -29,7 +29,7 @@ const style = () => import { resources } from '../assets/resources' -class LoginTabs extends React.Component & { classes: any }, { value: number }> { +export class LoginTabs extends React.Component & { classes: any }, { value: number }> { constructor(props: LoginTabs['props']) { super(props) this.state = { @@ -37,21 +37,18 @@ class LoginTabs extends React.Component & { classes: an } this.handleChange = this.handleChange.bind(this) } - public handleChange = (value: number) => { + + public handleChange = (_event: any, value: number) => { this.setState({ value }) return value === 0 ? this.props.history.push('/login') : this.props.history.push('/registration') } + public render() { const { value } = this.state return (
- this.handleChange(value)} - variant="fullWidth" - indicatorColor="primary" - centered={true}> + e.messageEntry), getBulkMessageKey) - const msgSegments: MessageBarState['digestedMessageEntries'] = [...lastState.digestedMessageEntries] + const msgSegments = [...lastState.digestedMessageEntries] for (const type in grouped) { if (grouped[type]) { const groupedEntries = grouped[type] diff --git a/examples/sn-dms-demo/src/components/Pickers/PathPicker.tsx b/examples/sn-dms-demo/src/components/Pickers/PathPicker.tsx index dc86aa0ef..1d598b66d 100644 --- a/examples/sn-dms-demo/src/components/Pickers/PathPicker.tsx +++ b/examples/sn-dms-demo/src/components/Pickers/PathPicker.tsx @@ -1,43 +1,28 @@ import Button from '@material-ui/core/Button' +import CircularProgress from '@material-ui/core/CircularProgress' import DialogActions from '@material-ui/core/DialogActions' import DialogContent from '@material-ui/core/DialogContent' -import List from '@material-ui/core/List' +import IconButton from '@material-ui/core/IconButton' +import ListItem from '@material-ui/core/ListItem/ListItem' import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemText from '@material-ui/core/ListItemText' -import MenuItem from '@material-ui/core/MenuItem' import Typography from '@material-ui/core/Typography' -import IconButton from '@material-ui/core/IconButton' -import { ODataParams } from '@sensenet/client-core' -import { GenericContent } from '@sensenet/default-content-types' +import { Injector } from '@furystack/inject' +import { ODataCollectionResponse, ODataParams, Repository } from '@sensenet/client-core' +import { Folder, GenericContent } from '@sensenet/default-content-types' import { Icon, iconType } from '@sensenet/icons-react' -import * as React from 'react' +import { ListPickerComponent } from '@sensenet/pickers-react' +import React from 'react' import Scrollbars from 'react-custom-scrollbars' import { connect } from 'react-redux' import MediaQuery from 'react-responsive' import * as DMSActions from '../../Actions' import { resources } from '../../assets/resources' -import { loadPickerItems, loadPickerParent, selectPickerItem, setBackLink } from '../../store/picker/actions' +import { selectPickerItem } from '../../store/picker/actions' import { rootStateType } from '../../store/rootReducer' import AddNewDialog from '../Dialogs/AddNewDialog' -const styles = { - selected: { - background: '#016d9e', - color: '#fff', - }, - iconsSelected: { - color: '#fff', - }, - textSelected: { - color: '#fff !important', - }, - openIcon: { - display: 'block', - color: '#fff', - }, -} - interface PathPickerProps { dialogComponent?: JSX.Element dialogTitle: string @@ -46,51 +31,31 @@ interface PathPickerProps { mode: string showAddFolder?: boolean loadOptions?: ODataParams + currentContent?: GenericContent + currentParent?: GenericContent } const mapStateToProps = (state: rootStateType) => { return { selectedTarget: state.dms.picker.selected, - items: state.dms.picker.items, pickerClose: state.dms.picker.pickerOnClose, - parent: state.dms.picker.parent, } } const mapDispatchToProps = { selectPickerItem, - loadPickerParent, - loadPickerItems, openDialog: DMSActions.openDialog, - closeDialog: DMSActions.closeDialog, - setBackLink, -} - -interface PathPickerState { - hovered: number | null } class PathPicker extends React.Component< - PathPickerProps & ReturnType & typeof mapDispatchToProps, - PathPickerState + PathPickerProps & ReturnType & typeof mapDispatchToProps > { - public state = { - hovered: null, - items: this.props.items, - } - public static getDerivedStateFromProps(newProps: PathPicker['props'], lastState: PathPicker['state']) { - if (newProps.parent && lastState.items.length !== newProps.items.length) { - newProps.loadPickerItems(newProps.parent.Path) - } - return { - ...lastState, - items: newProps.items, - } - } + private items: GenericContent[] = [] public handleClose = () => { this.props.pickerClose() } + public handleSubmit = () => { const { dialogComponent, dialogTitle, dialogCallback } = this.props if (dialogComponent) { @@ -98,29 +63,12 @@ class PathPicker extends React.Component< } this.props.onSelect && this.props.onSelect(this.props.selectedTarget[0]) } - public isSelected = (id: number) => { - return this.props.selectedTarget.findIndex(item => item.Id === id) > -1 - } - public handleClick = (e: React.MouseEvent, content: GenericContent, select: boolean = true) => { - e.stopPropagation() - e.preventDefault() - select ? this.props.selectPickerItem(content) : this.handleLoading(content.Id) - } - public handleMouseOver = (id: number) => { - this.setState({ - hovered: id, - }) - } - public handleMouseOut = () => { - this.setState({ - hovered: null, - }) - } - public isHovered = (id: number) => { - return this.state.hovered === id - } + public hasChildren = (id: number) => { - const content = this.props.items.find(item => item.Id === id) as any + const content = this.items.find(item => item.Id === id) as any + if (!content) { + return false + } // tslint:disable-next-line:no-string-literal return content['Children'] ? content.Children.filter((child: GenericContent) => child.IsFolder).length > 0 @@ -128,77 +76,102 @@ class PathPicker extends React.Component< : false : false } - public handleLoading = (id: number) => { - const content = this.props.items.find(item => item.Id === id) as GenericContent - this.props.loadPickerParent(id) - this.props.loadPickerItems(content ? content.Path : '') - this.props.setBackLink(true) - } + public handleAddNewClose = () => { // TODO } + public handleAddNewClick = () => { - const { parent, openDialog } = this.props + const { currentParent, openDialog } = this.props openDialog( - , + , resources.ADD_NEW, this.handleAddNewClose, ) } + + public onSelectionChanged = (node: GenericContent) => { + this.props.selectPickerItem(node) + } + + public loadItems = async (path: string) => { + let result: ODataCollectionResponse + const repository = Injector.Default.GetInstance(Repository) + const pickerItemOptions: ODataParams = { + select: ['DisplayName', 'Path', 'Id', 'Children/IsFolder'] as any, + expand: ['Children'] as any, + filter: "(isOf('Folder') and not isOf('SystemFolder'))", + metadata: 'no', + orderby: 'DisplayName', + } + + try { + result = await repository.loadCollection({ + path, + oDataOptions: { ...pickerItemOptions, ...this.props.loadOptions }, + }) + } catch (error) { + throw error + } + + this.items = result.d.results + return result.d.results + } + + public loadParent = async (id?: number) => { + const pickerParentOptions: ODataParams = { + select: ['DisplayName', 'Path', 'Id', 'ParentId', 'Workspace'], + expand: ['Workspace'], + metadata: 'no', + } + const repository = Injector.Default.GetInstance(Repository) + const result = await repository.load({ + idOrPath: id as number, + oDataOptions: { ...pickerParentOptions }, + }) + return result.d as GenericContent + } + + public renderItem = (renderItemProps: GenericContent) => ( + node.Id === renderItemProps.Id)}> + + + + + {this.hasChildren(renderItemProps.Id) ? ( + + ) : null} + + ) + public render() { - const { selectedTarget } = this.props - const { items } = this.state + const { selectedTarget, currentParent } = this.props return ( -
+ <> (
)} thumbMinSize={180}> - - {items.map(item => { - return ( - this.handleClick(e, item)} - onMouseEnter={() => this.handleMouseOver(item.Id)} - onMouseLeave={() => this.handleMouseOut()} - selected={this.isSelected(item.Id)}> - this.handleClick(e, item, false)}> - - - - {item.DisplayName} - - } - /> - {this.hasChildren(item.Id) ? ( - ) => this.handleClick(e, item)} - /> - ) : null} - - ) - })} - + this.props.selectPickerItem(null)} + renderLoading={() => ( +
+ +
+ )} + renderItem={this.renderItem} + loadItems={this.loadItems} + loadParent={this.loadParent} + parentId={currentParent && currentParent.ParentId} + currentPath={currentParent && currentParent.Path} + /> @@ -233,7 +206,7 @@ class PathPicker extends React.Component< )} -
+ ) } } diff --git a/examples/sn-dms-demo/src/components/Pickers/PickerBase.tsx b/examples/sn-dms-demo/src/components/Pickers/PickerBase.tsx index 930577350..75e308e4e 100644 --- a/examples/sn-dms-demo/src/components/Pickers/PickerBase.tsx +++ b/examples/sn-dms-demo/src/components/Pickers/PickerBase.tsx @@ -12,43 +12,17 @@ import { connect } from 'react-redux' import MediaQuery from 'react-responsive' import { pickerTheme } from '../../assets/picker' import { resources } from '../../assets/resources' -import { - deselectPickeritem, - loadPickerItems, - loadPickerParent, - selectPickerItem, - setBackLink, - setPickerParent, -} from '../../store/picker/actions' import { rootStateType } from '../../store/rootReducer' -// tslint:disable-next-line:no-var-requires -const sensenetLogo = require('../../assets/sensenet_white.png') - const mapStateToProps = (state: rootStateType) => { return { open: state.dms.picker.isOpened, - anchorElement: state.dms.actionmenu.anchorElement, onClose: state.dms.picker.pickerOnClose, - parent: state.dms.picker.parent, - items: state.dms.picker.items, - selected: state.dms.documentLibrary.selected, - closestWs: state.dms.picker.closestWorkspace, - backLink: state.dms.picker.backLink, pickerContent: state.dms.picker.content, pickerMode: state.dms.picker.mode, } } -const mapDispatchToProps = { - selectPickerItem, - deselectPickeritem, - loadPickerParent, - loadPickerItems, - setPickerParent, - setBackLink, -} - const styles = { closeButton: { position: 'absolute', @@ -91,62 +65,27 @@ const styles = { }, } -class Picker extends React.Component & typeof mapDispatchToProps, {}> { +class Picker extends React.Component, {}> { constructor(props: Picker['props']) { super(props) } + public handleClose = () => { this.props.onClose() - this.props.setBackLink(true) - } - public isLastItem = () => { - const { parent, closestWs } = this.props - return parent && closestWs ? parent.Path === closestWs.Path : false - } - public handleClickBack = () => { - const { parent } = this.props - if (this.isLastItem()) { - this.props.setBackLink(false) - const snContent = { - DisplayName: 'sensenet', - Workspace: { - Path: null, - }, - } as any - this.props.setPickerParent(snContent) - this.props.loadPickerItems('/', { - query: 'TypeIs:Workspace -TypeIs:Site', - select: ['DisplayName', 'Id', 'Path', 'Children'], - orderby: [['DisplayName', 'asc']], - }) - this.props.deselectPickeritem() - } else { - this.props.loadPickerParent(parent && parent.ParentId ? parent.ParentId : '') - this.props.loadPickerItems(parent ? parent.Path.substr(0, parent.Path.length - (parent.Name.length + 1)) : '') - this.props.deselectPickeritem() - } } + public render() { - const { backLink, open, parent } = this.props + const { open } = this.props return ( {matches => ( {matches ? ( - + - {backLink ? ( - this.handleClickBack()}> - - - ) : ( -
- sensenet -
- )} - {parent ? parent.DisplayName : 'Move content'} + {resources[this.props.pickerMode.toUpperCase()]} & typeof
- {backLink ? ( - this.handleClickBack()}> - - - ) : ( -
- sensenet -
- )} - {parent ? parent.DisplayName : 'Move content'} + {resources[this.props.pickerMode.toUpperCase()]}
@@ -194,7 +124,4 @@ class Picker extends React.Component & typeof } } -export default connect( - mapStateToProps, - mapDispatchToProps, -)(Picker) +export default connect(mapStateToProps)(Picker) diff --git a/examples/sn-dms-demo/src/components/Search/SearchDocuments.tsx b/examples/sn-dms-demo/src/components/Search/SearchDocuments.tsx index 0e84d0036..2b51749be 100644 --- a/examples/sn-dms-demo/src/components/Search/SearchDocuments.tsx +++ b/examples/sn-dms-demo/src/components/Search/SearchDocuments.tsx @@ -121,7 +121,7 @@ class SearchDocuments extends React.Component< if (innerQuery.toString()) { this.setState({ query: new Query(q => - q.query(typeQuery => typeQuery.type(SnFile).or.type(Folder)).and.query(innerQuery), + q.query(typeQuery => typeQuery.typeIs(SnFile).or.typeIs(Folder)).and.query(innerQuery), ).toString(), }) } else { diff --git a/examples/sn-dms-demo/src/components/Upload/UploadButton.tsx b/examples/sn-dms-demo/src/components/Upload/UploadButton.tsx index cdec29453..b9f85052d 100644 --- a/examples/sn-dms-demo/src/components/Upload/UploadButton.tsx +++ b/examples/sn-dms-demo/src/components/Upload/UploadButton.tsx @@ -1,5 +1,5 @@ -import { ClickAwayListener } from '@material-ui/core' import Button from '@material-ui/core/Button' +import ClickAwayListener from '@material-ui/core/ClickAwayListener' import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemText from '@material-ui/core/ListItemText' import Menu from '@material-ui/core/Menu' @@ -39,9 +39,9 @@ export interface UploadButtonState { anchorElement: HTMLElement | undefined } -export const UPLOAD_FILE_BUTTON_ID: string = 'sn-dms-upload-button' -export const UPLOAD_FOLDER_BUTTON_ID: string = 'sn-dms-upload-button' -export const UPLOAD_MENU_ID: string = 'sn-dms-upload-button' +export const UPLOAD_FILE_BUTTON_ID = 'sn-dms-upload-button' +export const UPLOAD_FOLDER_BUTTON_ID = 'sn-dms-upload-button' +export const UPLOAD_MENU_ID = 'sn-dms-upload-button' export class UploadButton extends React.Component { private readonly uploadFileButtonId = `${UPLOAD_FILE_BUTTON_ID}-${v1()}` diff --git a/examples/sn-dms-demo/src/components/UserProfile.tsx b/examples/sn-dms-demo/src/components/UserProfile.tsx index 80c5bf687..05722f100 100644 --- a/examples/sn-dms-demo/src/components/UserProfile.tsx +++ b/examples/sn-dms-demo/src/components/UserProfile.tsx @@ -155,11 +155,13 @@ class UserProfile extends React.Component<
- + {this.props.user ? ( + + ) : null}
@@ -209,10 +211,12 @@ class UserProfile extends React.Component< ) }} onRequestOrderChange={(field, direction) => { - this.props.updateChildrenOptions({ - ...this.props.childrenOptions, - orderby: [[field, direction]], - }) + if (field !== 'Workspace' && field !== 'Actions') { + this.props.updateChildrenOptions({ + ...this.props.childrenOptions, + orderby: [[field, direction]], + }) + } }} onItemClick={(ev, content) => { if ( diff --git a/examples/sn-dms-demo/src/components/UsersAndGroups/DeleteUserFromGroup.tsx b/examples/sn-dms-demo/src/components/UsersAndGroups/DeleteUserFromGroup.tsx index 4ebb01b67..397aabf3d 100644 --- a/examples/sn-dms-demo/src/components/UsersAndGroups/DeleteUserFromGroup.tsx +++ b/examples/sn-dms-demo/src/components/UsersAndGroups/DeleteUserFromGroup.tsx @@ -1,4 +1,5 @@ -import { Button, TableCell } from '@material-ui/core' +import Button from '@material-ui/core/Button' +import TableCell from '@material-ui/core/TableCell' import { Group, User } from '@sensenet/default-content-types' import { Icon } from '@sensenet/icons-react' import * as React from 'react' diff --git a/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupListItem.tsx b/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupListItem.tsx index c3717a033..fbc903905 100644 --- a/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupListItem.tsx +++ b/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupListItem.tsx @@ -1,3 +1,4 @@ +import { Tooltip } from '@material-ui/core' import IconButton from '@material-ui/core/IconButton' import ListItemIcon from '@material-ui/core/ListItemIcon' import ListItemText from '@material-ui/core/ListItemText' @@ -91,8 +92,11 @@ class GroupListItem extends React.Component< constructor(props: GroupListItem['props']) { super(props) } - public checkboxClick = (group: Group | null) => { - this.props.selectGroup(group ? [...this.props.groups, group] : [...this.props.groups]) + public checkboxClick = (group: Group) => { + const index = this.props.groups.findIndex(g => g.Id === group.Id) + index === -1 + ? this.props.selectGroup([...this.props.groups, group]) + : this.props.selectGroup([...this.props.groups.slice(0, index), ...this.props.groups.slice(index + 1)]) this.setState({ selected: !this.state.selected, }) @@ -105,7 +109,7 @@ class GroupListItem extends React.Component< this.checkboxClick(group)}> + onClick={() => this.checkboxClick(group as Group)}> - + + + ) } diff --git a/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupSearch.tsx b/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupSearch.tsx index 90b1c648d..fbc40b0ce 100644 --- a/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupSearch.tsx +++ b/examples/sn-dms-demo/src/components/UsersAndGroups/GroupSelector/GroupSearch.tsx @@ -1,6 +1,6 @@ -import { Theme } from '@material-ui/core' import FormControl from '@material-ui/core/FormControl' import InputAdornment from '@material-ui/core/InputAdornment' +import { Theme } from '@material-ui/core/styles/createMuiTheme' import withStyles, { WithStyles } from '@material-ui/core/styles/withStyles' import TextField from '@material-ui/core/TextField' import { Icon, iconType } from '@sensenet/icons-react' diff --git a/examples/sn-dms-demo/src/components/UsersAndGroups/UserInfo.tsx b/examples/sn-dms-demo/src/components/UsersAndGroups/UserInfo.tsx index 7dbf359ba..f8a773e7d 100644 --- a/examples/sn-dms-demo/src/components/UsersAndGroups/UserInfo.tsx +++ b/examples/sn-dms-demo/src/components/UsersAndGroups/UserInfo.tsx @@ -1,4 +1,3 @@ -import { Typography } from '@material-ui/core' import Avatar from '@material-ui/core/Avatar' import Paper from '@material-ui/core/Paper' import { Icon } from '@sensenet/icons-react' @@ -8,6 +7,7 @@ import * as DMSActions from '../../Actions' import { rootStateType } from '../../store/rootReducer' import EditPropertiesDialog from '../Dialogs/EditPropertiesDialog' +import Typography from '@material-ui/core/Typography' import { resources } from '../../assets/resources' // tslint:disable-next-line:no-var-requires diff --git a/examples/sn-dms-demo/src/components/WorkspaceSelector/WorkspaceSearch.tsx b/examples/sn-dms-demo/src/components/WorkspaceSelector/WorkspaceSearch.tsx index 3f7b20f8f..3c7a61708 100644 --- a/examples/sn-dms-demo/src/components/WorkspaceSelector/WorkspaceSearch.tsx +++ b/examples/sn-dms-demo/src/components/WorkspaceSelector/WorkspaceSearch.tsx @@ -1,6 +1,6 @@ -import { Theme } from '@material-ui/core' import FormControl from '@material-ui/core/FormControl' import InputAdornment from '@material-ui/core/InputAdornment' +import { Theme } from '@material-ui/core/styles/createMuiTheme' import withStyles, { WithStyles } from '@material-ui/core/styles/withStyles' import TextField from '@material-ui/core/TextField' import { Icon, iconType } from '@sensenet/icons-react' diff --git a/examples/sn-dms-demo/src/components/__tests__/LoginTabs.test.tsx b/examples/sn-dms-demo/src/components/__tests__/LoginTabs.test.tsx index e123bd762..2e35cb199 100644 --- a/examples/sn-dms-demo/src/components/__tests__/LoginTabs.test.tsx +++ b/examples/sn-dms-demo/src/components/__tests__/LoginTabs.test.tsx @@ -1,14 +1,41 @@ -import * as React from 'react' -import * as ReactDOM from 'react-dom' +import Tabs from '@material-ui/core/Tabs' +import { shallow } from 'enzyme' +import React from 'react' +import ReactDOM from 'react-dom' import { MemoryRouter } from 'react-router-dom' -import LoginTabs from '../LoginTabs' - -it('renders without crashing', () => { - const div = document.createElement('div') - ReactDOM.render( - - - , - div, - ) +import LoginTabsWithRouter, { LoginTabs } from '../LoginTabs' + +describe('Login tabs component', () => { + it('renders without crashing', () => { + const div = document.createElement('div') + ReactDOM.render( + + + , + div, + ) + }) + + const mock: any = jest.fn() + + it('should exsits', () => { + const wrapper = shallow() + expect(wrapper.find(Tabs).exists()).toBe(true) + }) + + it('should handle change', async () => { + const historyPush = jest.fn() + const wrapper = shallow( + , + ) + wrapper.find(Tabs).simulate('change', { event: '' }, 1) + + expect(historyPush).toBeCalled() + expect(historyPush).toBeCalledWith('/registration') + + wrapper.find(Tabs).simulate('change', { event: '' }, 0) + + expect(historyPush).toBeCalledTimes(2) + expect(historyPush).toBeCalledWith('/login') + }) }) diff --git a/examples/sn-dms-demo/src/pages/Dashboard.tsx b/examples/sn-dms-demo/src/pages/Dashboard.tsx index a6bc98258..afb07f8b8 100644 --- a/examples/sn-dms-demo/src/pages/Dashboard.tsx +++ b/examples/sn-dms-demo/src/pages/Dashboard.tsx @@ -160,6 +160,7 @@ class DashboardComponent extends React.Component< ) => ( @@ -222,6 +223,7 @@ class DashboardComponent extends React.Component< ) => ( @@ -248,6 +250,7 @@ class DashboardComponent extends React.Component< /> ) => ( ({ +export const addLogEntry = createAction((entry: AddLogEntry) => ({ type: 'SN_DMS_ADD_LOG_ENTRY', entry, -}) +})) -export const readLogEntries = (entries: LogEntry[]) => ({ +export const readLogEntries = createAction((entries: LogEntry[]) => ({ type: 'SN_DMS_READ_LOG_ENTRIES', entries, -}) +})) -export const initLog: () => InjectableAction> = () => ({ +export const initLog = createAction(() => ({ type: 'SN_DMS_INIT_LOG', - inject: async options => { + // tslint:disable-next-line: no-unnecessary-type-annotation + inject: async (options: IInjectableActionCallbackParams) => { const repository = options.getInjectable(Repository) const eventHub = new EventHub(repository) eventHub.onContentCreated.subscribe(ev => { @@ -204,4 +206,4 @@ export const initLog: () => InjectableAction = (state = defaultLogState, action) => { - switch (action.type) { - case 'SN_DMS_INIT_LOG': - return { - ...state, - isInitialized: true, - } - case 'SN_DMS_ADD_LOG_ENTRY': - return { - ...state, - entries: [ - ...state.entries, - { - ...(action as ReturnType).entry, - created: new Date(), - unread: true, - uuid: v1(), - }, - ], - } - case 'SN_DMS_READ_LOG_ENTRIES': - return { - ...state, - entries: state.entries.map(e => { - const a = action as ReturnType - if (a.entries.find(actionEntry => actionEntry.uuid === e.uuid)) { - return { - ...e, - unread: false, - } + if (isFromAction(action, initLog)) { + return { + ...state, + isInitialized: true, + } + } + if (isFromAction(action, addLogEntry)) { + return { + ...state, + entries: [ + ...state.entries, + { + ...action.entry, + created: new Date(), + unread: true, + uuid: v1(), + }, + ], + } + } + if (isFromAction(action, readLogEntries)) { + return { + ...state, + entries: state.entries.map(e => { + if (action.entries.find(actionEntry => actionEntry.uuid === e.uuid)) { + return { + ...e, + unread: false, } - return e - }), - } - default: - return state + } + return e + }), + } } + return state } diff --git a/examples/sn-dms-demo/src/store/documentlibrary/actions.ts b/examples/sn-dms-demo/src/store/documentlibrary/actions.ts index ead5bd906..b10c1ca11 100644 --- a/examples/sn-dms-demo/src/store/documentlibrary/actions.ts +++ b/examples/sn-dms-demo/src/store/documentlibrary/actions.ts @@ -1,131 +1,127 @@ import { ODataCollectionResponse, ODataParams, Repository } from '@sensenet/client-core' import { ValueObserver } from '@sensenet/client-utils' import { GenericContent } from '@sensenet/default-content-types' +import { createAction } from '@sensenet/redux' import { EventHub } from '@sensenet/repository-events' -import { Action } from 'redux' -import { InjectableAction } from 'redux-di-middleware' +import { IInjectableActionCallbackParams } from 'redux-di-middleware' import { changedContent, debounceReloadOnProgress } from '../../Actions' import { rootStateType } from '../../store/rootReducer' import { DocumentLibraryState, loadChunkSize } from './reducers' const eventObservables: Array> = [] -export const startLoadingParent = (idOrPath: number | string) => ({ +export const startLoadingParent = createAction((idOrPath: number | string) => ({ type: 'DMS_DOCLIB_LOADING_PARENT', idOrPath, -}) +})) -export const finishLoadingParent = () => ({ +export const finishLoadingParent = createAction(() => ({ type: 'DMS_DOCLIB_FINISH_LOADING_PARENT', -}) +})) -export const startLoadingChildren = (idOrPath: number | string) => ({ +export const startLoadingChildren = createAction((idOrPath: number | string) => ({ type: 'DMS_DOCLIB_LOADING_CHILDREN', idOrPath, -}) +})) -export const finishLoadingChildren = () => ({ +export const finishLoadingChildren = createAction(() => ({ type: 'DMS_DOCLIB_FINISH_LOADING_CHILDREN', -}) -export const loadParent: ( - idOrPath: string | number, - loadParentOptions?: ODataParams, -) => InjectableAction = ( - idOrPath: number | string, - loadParentOptions?: ODataParams, -) => ({ - type: 'DMS_DOCLIB_LOAD_PARENT', - inject: async options => { - const prevState = options.getState().dms.documentLibrary - if (prevState.parentIdOrPath === idOrPath) { - return - } - - eventObservables.forEach(o => o.dispose()) - eventObservables.length = 0 - - const eventHub = options.getInjectable(EventHub) - - options.dispatch(startLoadingParent(idOrPath)) - options.dispatch(startLoadingChildren(idOrPath)) - try { - const repository = options.getInjectable(Repository) - const newParent = await repository.load({ - idOrPath, - oDataOptions: { - ...prevState.parentOptions, - ...loadParentOptions, - }, - }) - options.dispatch(setParent(newParent.d)) - const emitChange = (content: GenericContent) => { - changedContent.push(content) - debounceReloadOnProgress(options.getState, options.dispatch) +})) +export const loadParent = createAction( + (idOrPath: number | string, loadParentOptions?: ODataParams) => ({ + type: 'DMS_DOCLIB_LOAD_PARENT', + // tslint:disable-next-line: no-unnecessary-type-annotation + inject: async (options: IInjectableActionCallbackParams) => { + const prevState = options.getState().dms.documentLibrary + if (prevState.parentIdOrPath === idOrPath) { + return } - eventObservables.push( - eventHub.onCustomActionExecuted.subscribe(value => { - const currentItems = options.getState().dms.documentLibrary.items - if ( - value.actionOptions.name !== 'GetExistingPreviewImages' && - (currentItems.d.results.filter(a => a.Id === value.actionOptions.idOrPath) || - currentItems.d.results.filter(a => a.Path === value.actionOptions.idOrPath)) - ) { - emitChange({ ParentId: newParent.d.Id } as GenericContent) - } - }) as any, - eventHub.onContentCreated.subscribe(value => emitChange(value.content)) as any, - eventHub.onContentModified.subscribe(value => emitChange(value.content)) as any, - eventHub.onContentDeleted.subscribe(value => { - const currentItems = options.getState().dms.documentLibrary.items - const filtered = currentItems.d.results.filter(item => item.Id !== value.contentData.Id) - options.dispatch( - setItems({ - ...currentItems, - d: { - __count: filtered.length, - results: filtered, - }, - }), - ) - }) as any, - eventHub.onContentMoved.subscribe(value => emitChange(value.content)) as any, - ) - const ancestors = await repository.executeAction>({ - idOrPath: newParent.d.Id, - method: 'GET', - name: 'Ancestors', - body: undefined, - oDataOptions: { - ...prevState.childrenOptions, - orderby: [['Path', 'asc']], - }, - }) - options.dispatch(setAncestors([...ancestors.d.results, newParent.d])) + eventObservables.forEach(o => o.dispose()) + eventObservables.length = 0 - options.dispatch( - setItems({ - d: { - __count: 0, - results: [], + const eventHub = options.getInjectable(EventHub) + + options.dispatch(startLoadingParent(idOrPath)) + options.dispatch(startLoadingChildren(idOrPath)) + try { + const repository = options.getInjectable(Repository) + const newParent = await repository.load({ + idOrPath, + oDataOptions: { + ...prevState.parentOptions, + ...loadParentOptions, }, - }), - ) - options.dispatch(finishLoadingChildren()) - options.dispatch(loadMore()) - } catch (error) { - options.dispatch(setError(error)) - } finally { - options.dispatch(finishLoadingParent()) - } - }, -}) + }) + options.dispatch(setParent(newParent.d)) + const emitChange = (content: GenericContent) => { + changedContent.push(content) + debounceReloadOnProgress(options.getState, options.dispatch) + } + + eventObservables.push( + eventHub.onCustomActionExecuted.subscribe(value => { + const currentItems = options.getState().dms.documentLibrary.items + if ( + value.actionOptions.name !== 'GetExistingPreviewImages' && + (currentItems.d.results.filter(a => a.Id === value.actionOptions.idOrPath) || + currentItems.d.results.filter(a => a.Path === value.actionOptions.idOrPath)) + ) { + emitChange({ ParentId: newParent.d.Id } as GenericContent) + } + }) as any, + eventHub.onContentCreated.subscribe(value => emitChange(value.content)) as any, + eventHub.onContentModified.subscribe(value => emitChange(value.content)) as any, + eventHub.onContentDeleted.subscribe(value => { + const currentItems = options.getState().dms.documentLibrary.items + const filtered = currentItems.d.results.filter(item => item.Id !== value.contentData.Id) + options.dispatch( + setItems({ + ...currentItems, + d: { + __count: filtered.length, + results: filtered, + }, + }), + ) + }) as any, + eventHub.onContentMoved.subscribe(value => emitChange(value.content)) as any, + ) + const ancestors = await repository.executeAction>({ + idOrPath: newParent.d.Id, + method: 'GET', + name: 'Ancestors', + body: undefined, + oDataOptions: { + ...prevState.childrenOptions, + orderby: [['Path', 'asc']], + }, + }) + options.dispatch(setAncestors([...ancestors.d.results, newParent.d])) + + options.dispatch( + setItems({ + d: { + __count: 0, + results: [], + }, + }), + ) + options.dispatch(finishLoadingChildren()) + options.dispatch(loadMore()) + } catch (error) { + options.dispatch(setError(error)) + } finally { + options.dispatch(finishLoadingParent()) + } + }, + }), +) -export const loadMore: (count?: number) => InjectableAction = ( - count: number = loadChunkSize, -) => ({ +export const loadMore = createAction((count: number = loadChunkSize) => ({ type: 'DMS_DOCLIB_LOAD_MORE', - inject: async options => { + // tslint:disable-next-line: no-unnecessary-type-annotation + inject: async (options: IInjectableActionCallbackParams) => { const currentDocLibState = options.getState().dms.documentLibrary if ( @@ -156,80 +152,77 @@ export const loadMore: (count?: number) => InjectableAction(content: T) => Action & { content: T } = ( - content: T, -) => ({ +export const setParent = createAction((content: T) => ({ type: 'DMS_DOCLIB_SET_PARENT', content, -}) +})) -export const setAncestors = (ancestors: T[]) => ({ +export const setAncestors = createAction((ancestors: T[]) => ({ type: 'DMS_DOCLIB_SET_ANCESTORS', ancestors, -}) +})) -export const setItems: ( - items: ODataCollectionResponse, -) => Action & { items: ODataCollectionResponse } = (items: ODataCollectionResponse) => ({ - type: 'DMS_DOCLIB_SET_ITEMS', - items, -}) +export const setItems = createAction( + (items: ODataCollectionResponse) => ({ + type: 'DMS_DOCLIB_SET_ITEMS', + items, + }), +) -export const setError = (error?: any) => ({ +export const setError = createAction((error?: any) => ({ type: 'DMS_DOCLIB_SET_ERROR', error, -}) +})) -export const select = (selected: T[]) => ({ +export const select = createAction((selected: T[]) => ({ type: 'DMS_DOCLIB_SELECT', selected, -}) +})) -export const setActive = (active?: T) => ({ +export const setActive = createAction((active?: T) => ({ type: 'DMS_DOCLIB_SET_ACTIVE', active, -}) - -export const updateChildrenOptions = (odataOptions: ODataParams) => - ({ - type: 'DMS_DOCLIB_UPDATE_CHILDREN_OPTIONS', - inject: async options => { - const currentState = options.getState() - const parentPath = currentState.dms.documentLibrary.parent ? currentState.dms.documentLibrary.parent.Path : '' - const repository = options.getInjectable(Repository) - options.dispatch( - startLoadingChildren( - currentState.dms.documentLibrary.parentIdOrPath ? currentState.dms.documentLibrary.parentIdOrPath : '', - ), - ) - try { - const items = await repository.loadCollection({ - path: parentPath, - oDataOptions: { - ...options.getState().dms.documentLibrary.childrenOptions, - ...odataOptions, - }, - }) - options.dispatch(setItems(items)) - } catch (error) { - options.dispatch(setError(error)) - } finally { - options.dispatch(finishLoadingChildren()) - options.dispatch(setChildrenOptions(odataOptions)) - } +})) - /** */ - }, - } as InjectableAction & { odataOptions: ODataParams }) +export const updateChildrenOptions = createAction((odataOptions: ODataParams) => ({ + type: 'DMS_DOCLIB_UPDATE_CHILDREN_OPTIONS', + odataOptions, + // tslint:disable-next-line: no-unnecessary-type-annotation + inject: async (options: IInjectableActionCallbackParams) => { + const currentState = options.getState() + const parentPath = currentState.dms.documentLibrary.parent ? currentState.dms.documentLibrary.parent.Path : '' + const repository = options.getInjectable(Repository) + options.dispatch( + startLoadingChildren( + currentState.dms.documentLibrary.parentIdOrPath ? currentState.dms.documentLibrary.parentIdOrPath : '', + ), + ) + try { + const items = await repository.loadCollection({ + path: parentPath, + oDataOptions: { + ...options.getState().dms.documentLibrary.childrenOptions, + ...odataOptions, + }, + }) + options.dispatch(setItems(items)) + } catch (error) { + options.dispatch(setError(error)) + } finally { + options.dispatch(finishLoadingChildren()) + options.dispatch(setChildrenOptions(odataOptions)) + } + }, +})) -export const updateSearchValues = (value: Partial) => ({ +export const updateSearchValues = createAction((value: Partial) => ({ type: 'DMS_DOCLIB_UPDATE_SEARCH_STATE', value, -}) +})) -export const setChildrenOptions = (odataOptions: ODataParams) => ({ +export const setChildrenOptions = createAction((odataOptions: ODataParams) => ({ type: 'DMS_DOCLIB_SET_CHILDREN_OPTIONS', odataOptions, -}) +})) diff --git a/examples/sn-dms-demo/src/store/documentlibrary/reducers.ts b/examples/sn-dms-demo/src/store/documentlibrary/reducers.ts index 2f3cf06ad..ac48899a5 100644 --- a/examples/sn-dms-demo/src/store/documentlibrary/reducers.ts +++ b/examples/sn-dms-demo/src/store/documentlibrary/reducers.ts @@ -1,16 +1,19 @@ import { ODataCollectionResponse, ODataParams } from '@sensenet/client-core' import { GenericContent } from '@sensenet/default-content-types' +import { isFromAction } from '@sensenet/redux' import { Reducer } from 'redux' import { + finishLoadingChildren, + finishLoadingParent, select, setActive, setAncestors, + setChildrenOptions, setError, setItems, setParent, startLoadingChildren, startLoadingParent, - updateChildrenOptions, updateSearchValues, } from './actions' @@ -101,80 +104,91 @@ export const defaultState: DocumentLibraryState = { } export const documentLibrary: Reducer = (state = defaultState, action) => { - switch (action.type) { - case 'DMS_DOCLIB_LOADING_PARENT': - return { - ...state, - isLoadingParent: true, - parentIdOrPath: (action as ReturnType).idOrPath, - } - case 'DMS_DOCLIB_FINISH_LOADING_PARENT': - return { - ...state, - isLoadingParent: false, - } - case 'DMS_DOCLIB_LOADING_CHILDREN': - return { - ...state, - isLoadingChildren: true, - parentIdOrPath: (action as ReturnType).idOrPath, - } - case 'DMS_DOCLIB_FINISH_LOADING_CHILDREN': - return { - ...state, - isLoadingChildren: false, - } - case 'DMS_DOCLIB_SET_PARENT': - return { - ...state, - parent: (action as ReturnType).content, - } - case 'DMS_DOCLIB_SET_ANCESTORS': - return { - ...state, - ancestors: (action as ReturnType).ancestors, - } - case 'DMS_DOCLIB_SET_ITEMS': - return { - ...state, - items: (action as ReturnType).items, - selected: [ - ...state.selected.filter(s => - action.items.d.results.find((i: GenericContent) => i.Id === s.Id) ? true : false, - ), - ], - } - case 'DMS_DOCLIB_SET_ERROR': - return { - ...state, - error: (action as ReturnType).error, - } - case 'DMS_DOCLIB_SELECT': - return { - ...state, - selected: (action as ReturnType).selected, - } - case 'DMS_DOCLIB_SET_ACTIVE': - return { - ...state, - active: (action as ReturnType).active, - } - case 'DMS_DOCLIB_SET_CHILDREN_OPTIONS': - return { - ...state, - childrenOptions: { - ...state.childrenOptions, - ...(action as ReturnType).odataOptions, - }, - } - case 'DMS_DOCLIB_UPDATE_SEARCH_STATE': - return { - ...state, - searchState: { - ...state.searchState, - ...(action as ReturnType).value, - }, - } + if (isFromAction(action, startLoadingParent)) { + return { + ...state, + isLoadingParent: true, + parentIdOrPath: action.idOrPath, + } + } + if (isFromAction(action, finishLoadingParent)) { + return { + ...state, + isLoadingParent: false, + } + } + if (isFromAction(action, startLoadingChildren)) { + return { + ...state, + isLoadingChildren: true, + parentIdOrPath: action.idOrPath, + } + } + if (isFromAction(action, finishLoadingChildren)) { + return { + ...state, + isLoadingChildren: false, + } + } + + if (isFromAction(action, setParent)) { + return { + ...state, + parent: action.content, + } + } + if (isFromAction(action, setAncestors)) { + return { + ...state, + ancestors: action.ancestors, + } + } + if (isFromAction(action, setItems)) { + return { + ...state, + items: action.items, + selected: [ + ...state.selected.filter(s => + action.items.d.results.find((i: GenericContent) => i.Id === s.Id) ? true : false, + ), + ], + } + } + if (isFromAction(action, setError)) { + return { + ...state, + error: action.error, + } + } + if (isFromAction(action, select)) { + return { + ...state, + selected: action.selected, + } + } + if (isFromAction(action, setActive)) { + return { + ...state, + active: action.active, + } + } + if (isFromAction(action, setChildrenOptions)) { + return { + ...state, + childrenOptions: { + ...state.childrenOptions, + ...action.odataOptions, + }, + } + } + if (isFromAction(action, updateSearchValues)) { + return { + ...state, + searchState: { + ...state.searchState, + ...action.value, + }, + } } return state } diff --git a/examples/sn-dms-demo/src/store/edited/reducers.ts b/examples/sn-dms-demo/src/store/edited/reducers.ts index 4cbabc47b..e91ee8f9b 100644 --- a/examples/sn-dms-demo/src/store/edited/reducers.ts +++ b/examples/sn-dms-demo/src/store/edited/reducers.ts @@ -2,10 +2,7 @@ import { GenericContent } from '@sensenet/default-content-types' import { loadContent, PromiseReturns } from '@sensenet/redux/dist/Actions' import { AnyAction, Reducer } from 'redux' -export const editedContent: Reducer = ( - state: GenericContent | null = null, - action: AnyAction, -) => { +export const editedContent: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'LOAD_EDITED_CONTENT_SUCCESS': return (action.result as PromiseReturns).d diff --git a/examples/sn-dms-demo/src/store/picker/reducers.ts b/examples/sn-dms-demo/src/store/picker/reducers.ts index c74c7cb11..64751e920 100644 --- a/examples/sn-dms-demo/src/store/picker/reducers.ts +++ b/examples/sn-dms-demo/src/store/picker/reducers.ts @@ -3,7 +3,7 @@ import { createContent, PromiseReturns } from '@sensenet/redux/dist/Actions' import { AnyAction, combineReducers, Reducer } from 'redux' import { loadPickerItems, loadPickerParent } from './actions' -export const pickerIsOpened: Reducer = (state: boolean = false, action: AnyAction) => { +export const pickerIsOpened: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'OPEN_PICKER': return true @@ -14,7 +14,7 @@ export const pickerIsOpened: Reducer = (state: boolean = false, action: } } -export const pickerOnClose: Reducer = (state: any = null, action: AnyAction) => { +export const pickerOnClose: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'OPEN_PICKER': return action.onClose @@ -25,10 +25,7 @@ export const pickerOnClose: Reducer = (state: any = null, action: AnyAction } } -export const pickerContent: Reducer = ( - state: GenericContent | null = null, - action: AnyAction, -) => { +export const pickerContent: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'OPEN_PICKER': return action.content @@ -39,10 +36,7 @@ export const pickerContent: Reducer = ( } } -export const pickerParent: Reducer = ( - state: GenericContent | null = null, - action: AnyAction, -) => { +export const pickerParent: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'SET_PICKER_PARENT': return action.content @@ -54,7 +48,7 @@ export const pickerParent: Reducer = ( } } -export const pickerItems: Reducer = (state: GenericContent[] = [], action: AnyAction) => { +export const pickerItems: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'LOAD_PICKER_ITEMS_SUCCESS': return (action.result as PromiseReturns).d.results @@ -66,7 +60,7 @@ export const pickerItems: Reducer = (state: GenericContent[] = } } -export const pickerSelected: Reducer = (state: GenericContent[] = [], action: AnyAction) => { +export const pickerSelected: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'SELECT_PICKER_ITEM': return action.content ? [action.content] : [] @@ -77,7 +71,7 @@ export const pickerSelected: Reducer = (state: GenericContent[ } } -export const pickerMode: Reducer = (state: string = 'move', action: AnyAction) => { +export const pickerMode: Reducer = (state = 'move', action: AnyAction) => { switch (action.type) { case 'OPEN_PICKER': return action.mode @@ -86,10 +80,7 @@ export const pickerMode: Reducer = (state: string = 'move', action: AnyA } } -export const closestWorkspace: Reducer = ( - state: GenericContent | null = null, - action: AnyAction, -) => { +export const closestWorkspace: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'SET_PICKER_PARENT': return action.content.Workspace.Path @@ -101,7 +92,7 @@ export const closestWorkspace: Reducer = ( } } -export const backLink: Reducer = (state: boolean = true, action: AnyAction) => { +export const backLink: Reducer = (state = true, action: AnyAction) => { switch (action.type) { case 'SET_BACKLINK': return action.state diff --git a/examples/sn-dms-demo/src/store/queries.ts b/examples/sn-dms-demo/src/store/queries.ts index 1a836b569..31e2eaf3f 100644 --- a/examples/sn-dms-demo/src/store/queries.ts +++ b/examples/sn-dms-demo/src/store/queries.ts @@ -1,58 +1,45 @@ -import { ODataBatchResponse, ODataParams, Repository } from '@sensenet/client-core' +import { ODataParams, Repository } from '@sensenet/client-core' import { Query } from '@sensenet/default-content-types' +import { createAction, isFromAction } from '@sensenet/redux' import { deleteContent, PromiseReturns, updateContent } from '@sensenet/redux/dist/Actions' -import { AnyAction, Reducer } from 'redux' -import { InjectableAction } from 'redux-di-middleware' +import { Reducer } from 'redux' +import { IInjectableActionCallbackParams } from 'redux-di-middleware' import { rootStateType } from './rootReducer' export type QueryType = 'Private' | 'Public' | 'NonDefined' -export const saveQuery: ( - idOrPath: string | number, - query: string, - displayName: string, - queryType: QueryType, -) => InjectableAction = ( - idOrPath: string | number, - query: string, - displayName: string, - queryType = 'Private', -) => ({ - type: 'SN_DMS_SAVE_QUERY', - inject: async options => { - const repo = options.getInjectable(Repository) - await repo.executeAction({ - idOrPath, - name: 'SaveQuery', - method: 'POST', - body: { - query, - displayName, - queryType, - }, - }) - options.dispatch(queriesRequested(0, 'Private')) - }, -}) +export const saveQuery = createAction( + (idOrPath: string | number, query: string, displayName: string, queryType = 'Private') => ({ + type: 'SN_DMS_SAVE_QUERY', + // tslint:disable-next-line: no-unnecessary-type-annotation + inject: async (options: IInjectableActionCallbackParams) => { + const repo = options.getInjectable(Repository) + await repo.executeAction({ + idOrPath, + name: 'SaveQuery', + method: 'POST', + body: { + query, + displayName, + queryType, + }, + }) + options.dispatch(queriesRequested(0, 'Private')) + }, + }), +) -export const getQueries: ( - idOrPath: string | number, - queryType: QueryType, - force?: boolean, -) => InjectableAction = ( - idOrPath: string | number, - queryType = 'Private', - force: boolean = false, -) => ({ +export const getQueries = createAction((idOrPath: string | number, queryType = 'Private', force: boolean = false) => ({ type: 'SN_DMS_GET_QUERIES', - inject: async options => { + // tslint:disable-next-line: no-unnecessary-type-annotation + inject: async (options: IInjectableActionCallbackParams) => { const state = options.getState() if (force === false && state.dms.queries.idOrPath === idOrPath && state.dms.queries.queryType === queryType) { return } options.dispatch(queriesRequested(idOrPath, queryType)) const repo = options.getInjectable(Repository) - const q: ODataBatchResponse = await repo.executeAction({ + const q = await repo.executeAction({ idOrPath, name: 'GetQueries', method: 'GET', @@ -65,30 +52,30 @@ export const getQueries: ( }) options.dispatch(queriesReceived(q.d.results)) }, -}) +})) -export const queriesRequested = (idOrPath: string | number, queryType = 'Private') => ({ +export const queriesRequested = createAction((idOrPath: string | number, queryType = 'Private') => ({ type: 'SN_DMS_DEMO_QUERIES_REQUESTED', idOrPath, queryType, -}) +})) -export const queriesReceived = (receivedQueries: Query[]) => ({ +export const queriesReceived = createAction((receivedQueries: Query[]) => ({ type: 'SN_DMS_DEMO_QUERIES_RECEIVED', receivedQueries, -}) +})) -export const select = (selected: T[]) => ({ +export const select = createAction((selected: T[]) => ({ type: 'SN_DMS_DEMO_QUERIES_SELECT', selected, -}) +})) -export const setActive = (active?: T) => ({ +export const setActive = createAction((active?: T) => ({ type: 'SN_DMS_DEMO_QUERIES_SET_ACTIVE', active, -}) +})) -interface QueriesType { +export interface QueriesType { idOrPath: number | string queryType: QueryType queries: Query[] @@ -99,48 +86,49 @@ interface QueriesType { export const queries: Reducer = ( state = { idOrPath: 0, queries: [], selected: [], queryType: 'Private', childrenOptions: {} }, - action: AnyAction, + action, ) => { - switch (action.type) { - case 'SN_DMS_DEMO_QUERIES_RECEIVED': - return { - ...state, - queries: action.receivedQueries, - } - case 'SN_DMS_DEMO_QUERIES_REQUESTED': - return { - ...state, - idOrPath: action.idOrPath, - queryType: action.queryType, - } - case 'SN_DMS_DEMO_QUERIES_SELECT': - return { - ...state, - selected: action.selected, - } - case 'SN_DMS_DEMO_QUERIES_SET_ACTIVE': - return { - ...state, - active: action.active, - } - case 'UPDATE_CONTENT_SUCCESS': - return { - ...state, - queries: state.queries.map(c => { - if (c.Id === (action.result as PromiseReturns).d.Id) { - return action.result.d - } - return c - }), - } - case 'DELETE_BATCH_SUCCESS': - case 'DELETE_CONTENT_SUCCESS': - const deletedIds = (action.result as PromiseReturns).d.results.map(d => d.Id) - return { - ...state, - queries: [...state.queries.filter(q => !(deletedIds as any).includes(q.Id))], - } - default: - return state + if (isFromAction(action, queriesReceived)) { + return { + ...state, + queries: action.receivedQueries, + } + } + if (isFromAction(action, queriesRequested)) { + return { + ...state, + idOrPath: action.idOrPath, + queryType: action.queryType, + } + } + if (isFromAction(action, select)) { + return { ...state, selected: action.selected } + } + if (isFromAction(action, setActive)) { + return { + ...state, + active: action.active, + } + } + /** todo: auto-generated success action from @sn/redux */ + if (action.type === 'UPDATE_CONTENT_SUCCESS') { + return { + ...state, + queries: state.queries.map(c => { + if (c.Id === (action.result as PromiseReturns).d.Id) { + return action.result.d + } + return c + }), + } + } + /** todo: auto-generated success action from @sn/redux */ + if (action.type === 'DELETE_CONTENT_SUCCESS') { + const deletedIds = (action.result as PromiseReturns).d.results.map(d => d.Id) + return { + ...state, + queries: [...state.queries.filter(q => !(deletedIds as any).includes(q.Id))], + } } + return state } diff --git a/examples/sn-dms-demo/src/store/usersandgroups/actions.ts b/examples/sn-dms-demo/src/store/usersandgroups/actions.ts index 2428d0bbf..53da8fe08 100644 --- a/examples/sn-dms-demo/src/store/usersandgroups/actions.ts +++ b/examples/sn-dms-demo/src/store/usersandgroups/actions.ts @@ -2,10 +2,10 @@ import { ODataCollectionResponse, ODataParams, Repository } from '@sensenet/clie import { ValueObserver } from '@sensenet/client-utils' import { ActionModel, GenericContent, Group, User } from '@sensenet/default-content-types' import { EventHub } from '@sensenet/repository-events' -import { Action, AnyAction } from 'redux' -import { InjectableAction } from 'redux-di-middleware' +import { Action } from 'redux' +import { IInjectableActionCallbackParams } from 'redux-di-middleware' import { changedContent } from '../../Actions' -import { arrayComparer } from '../../assets/helpers' +import { arrayComparer, arrayDiff } from '../../assets/helpers' import { rootStateType } from '../../store/rootReducer' const eventObservables: Array> = [] @@ -15,15 +15,9 @@ export const startLoading = (idOrPath: number | string) => ({ idOrPath, }) -export const loadUser: ( - idOrPath: string | number, - userOptions?: ODataParams, -) => InjectableAction = ( - idOrPath: number | string, - userOptions?: ODataParams, -) => ({ +export const loadUser = (idOrPath: number | string, userOptions?: ODataParams) => ({ type: 'DMS_USERSANDGROUPS_LOAD_USER', - inject: async options => { + inject: async (options: IInjectableActionCallbackParams) => { const prevState = options.getState().dms.usersAndGroups if (prevState.user.currentUser && prevState.user.currentUser.Id.toString() === idOrPath) { return @@ -50,10 +44,10 @@ export const loadUser: ( eventObservables.push( eventHub.onCustomActionExecuted.subscribe(() => { emitChange({ Id: newUser.d.Id } as User) - }) as any, - eventHub.onContentCreated.subscribe(value => emitChange(value.content)) as any, - eventHub.onContentModified.subscribe(value => emitChange(value.content)) as any, - eventHub.onContentMoved.subscribe(value => emitChange(value.content)) as any, + }), + eventHub.onContentCreated.subscribe(value => emitChange(value.content)), + eventHub.onContentModified.subscribe(value => emitChange(value.content)), + eventHub.onContentMoved.subscribe(value => emitChange(value.content)), ) await Promise.all([ @@ -119,23 +113,22 @@ export const setGroupOptions = (odataOptions: ODataPar odataOptions, }) -export const userIsAdmin = (userPath: string) => - ({ - type: 'DMS_USER_ISADMIN', - inject: async options => { - const repository = options.getInjectable(Repository) - const payload = await repository.security.getParentGroups({ - contentIdOrPath: userPath, - directOnly: false, - oDataOptions: { - select: 'Name', - }, - }) - const groups = payload.d.results as Group[] - const admin = groups.find(group => group.Name === 'DMSAdmins') - options.dispatch(isAdmin(admin ? true : false)) - }, - } as InjectableAction) +export const userIsAdmin = (userPath: string) => ({ + type: 'DMS_USER_ISADMIN', + inject: async (options: IInjectableActionCallbackParams) => { + const repository = options.getInjectable(Repository) + const payload = await repository.security.getParentGroups({ + contentIdOrPath: userPath, + directOnly: false, + oDataOptions: { + select: 'Name', + }, + }) + const groups = payload.d.results as Group[] + const admin = groups.find(group => group.Name === 'DMSAdmins') + options.dispatch(isAdmin(admin ? true : false)) + }, +}) export const isAdmin = (admin: boolean = false) => ({ type: 'DMS_USER_ISADMIN', @@ -147,76 +140,72 @@ export const setActive = (active?: T) => ({ active, }) -export const updateChildrenOptions = (o: ODataParams) => - ({ - type: 'DMS_USERSANDGROUPS_UPDATE_CHILDREN_OPTIONS', - inject: async options => { - const currentState = options.getState() - const repository = options.getInjectable(Repository) - options.dispatch( - startLoading( - currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', - ), - ) - try { - const items = await repository.security.getParentGroups({ - contentIdOrPath: currentState.dms.usersAndGroups.user.currentUser - ? currentState.dms.usersAndGroups.user.currentUser.Id - : 0, - directOnly: false, - oDataOptions: { - ...{ - select: ['Workspace', 'DisplayName', 'Type', 'Id', 'Path', 'Actions', 'Icon', 'Members'], - expand: ['Workspace', 'Actions', 'Members'], - filter: `isOf('Group')`, - }, - ...(o as any), +export const updateChildrenOptions = (o: ODataParams) => ({ + type: 'DMS_USERSANDGROUPS_UPDATE_CHILDREN_OPTIONS', + inject: async (options: IInjectableActionCallbackParams) => { + const currentState = options.getState() + const repository = options.getInjectable(Repository) + options.dispatch( + startLoading( + currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', + ), + ) + try { + const items = await repository.security.getParentGroups({ + contentIdOrPath: currentState.dms.usersAndGroups.user.currentUser + ? currentState.dms.usersAndGroups.user.currentUser.Id + : 0, + directOnly: false, + oDataOptions: { + ...{ + select: ['Workspace', 'DisplayName', 'Type', 'Id', 'Path', 'Actions', 'Icon', 'Members'], + expand: ['Workspace', 'Actions', 'Members'], + filter: `isOf('Group')`, }, - }) - options.dispatch(setMemberships(items)) - } catch (error) { - options.dispatch(setError(error)) - } finally { - options.dispatch(finishLoading()) - options.dispatch(setChildrenOptions(o)) - } - - /** */ - }, - } as InjectableAction & { odataOptions: ODataParams }) + ...(o as any), + }, + }) + options.dispatch(setMemberships(items)) + } catch (error) { + options.dispatch(setError(error)) + } finally { + options.dispatch(finishLoading()) + options.dispatch(setChildrenOptions(o)) + } + }, +}) export const setChildrenOptions = (odataOptions: ODataParams) => ({ type: 'DMS_USERSANDGROUPS_SET_CHILDREN_OPTIONS', odataOptions, }) -export const removeMemberFromGroups = (contentIds: number[], groups: Group[]) => - ({ - type: 'DMS_USERSANDGROUPS_REMOVE_MEMBER', - inject: async options => { - const currentState = options.getState() - const repository = options.getInjectable(Repository) - options.dispatch( - startLoading( - currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', - ), - ) - try { - const remove = groups.map(async group => { - return await repository.security.removeMembers(group.Id, contentIds) - }) - await Promise.all(remove) - } catch (error) { - options.dispatch(setError(error)) - } finally { - const comparedList: Group[] = arrayComparer(groups, currentState.dms.usersAndGroups.user.memberships.d.results) - options.dispatch(updateGroupList({ d: { __count: comparedList.length, results: comparedList } })) - options.dispatch(loadUser(contentIds[0])) - options.dispatch(finishLoading()) - options.dispatch(getGroups(currentState.dms.usersAndGroups.user.memberships)) - } - }, - } as InjectableAction & { odataOptions: ODataParams }) +export const removeMemberFromGroups = (contentIds: number[], groups: Group[]) => ({ + type: 'DMS_USERSANDGROUPS_REMOVE_MEMBER', + inject: async (options: IInjectableActionCallbackParams) => { + const currentState = options.getState() + const repository = options.getInjectable(Repository) + options.dispatch( + startLoading( + currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', + ), + ) + try { + const remove = groups.map(async group => { + return await repository.security.removeMembers(group.Id, contentIds) + }) + await Promise.all(remove) + } catch (error) { + options.dispatch(setError(error)) + } finally { + const comparedList = arrayComparer(groups, currentState.dms.usersAndGroups.user.memberships.d.results) + options.dispatch(updateGroupList({ d: { __count: comparedList.length, results: comparedList } })) + options.dispatch(loadUser(contentIds[0])) + options.dispatch(finishLoading()) + options.dispatch(getGroups(currentState.dms.usersAndGroups.user.memberships)) + } + }, +}) export const selectGroup = (groups: GenericContent[] | GenericContent) => { return { @@ -230,45 +219,43 @@ export const deselectGroup = (id: number) => ({ id, }) -export const getGroups = (memberships: ODataCollectionResponse) => - ({ - type: 'DMS_USERSANDGROUPS_GET_GROUPS', - inject: async options => { - const currentState = options.getState() - const repository = options.getInjectable(Repository) - options.dispatch( - startLoading( - currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', - ), - ) - try { - const groups = await repository.loadCollection({ - path: '/Root', - oDataOptions: { - query: '+TypeIs:Group', - select: ['DisplayName', 'Path', 'Actions'] as any, - expand: ['Actions'] as any, - }, - }) - const comparedList: Group[] = arrayComparer(groups.d.results, memberships.d.results) - const newGroups = { - d: { - __count: comparedList.length, - results: comparedList.filter((group: Group) => { - const actions = group.Actions as ActionModel[] - return actions ? actions.find((action: ActionModel) => action.Name === 'Edit') : [] - }), - }, - } - options.dispatch(setGroups(newGroups)) - } catch (error) { - options.dispatch(setError(error)) - } finally { - options.dispatch(finishLoading()) +export const getGroups = (memberships: ODataCollectionResponse) => ({ + type: 'DMS_USERSANDGROUPS_GET_GROUPS', + inject: async (options: IInjectableActionCallbackParams) => { + const currentState = options.getState() + const repository = options.getInjectable(Repository) + options.dispatch( + startLoading( + currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', + ), + ) + try { + const groups = await repository.loadCollection({ + path: '/Root', + oDataOptions: { + query: '+TypeIs:Group', + select: ['DisplayName', 'Path', 'Actions'] as any, + expand: ['Actions'] as any, + }, + }) + const comparedList = arrayDiff(groups.d.results, memberships.d.results) + const newGroups = { + d: { + __count: comparedList.length, + results: comparedList.filter((group: Group) => { + const actions = group.Actions as ActionModel[] + return actions ? actions.find((action: ActionModel) => action.Name === 'Edit') : [] + }), + }, } - }, - } as InjectableAction & { odataOptions: ODataParams }) - + options.dispatch(setGroups(newGroups)) + } catch (error) { + options.dispatch(setError(error)) + } finally { + options.dispatch(finishLoading()) + } + }, +}) export const setGroups = (groups: ODataCollectionResponse) => ({ type: 'DMS_USERSANDGROUPS_SET_GROUPS', groups, @@ -283,32 +270,31 @@ export const clearSelection = () => ({ type: 'DMS_USERSANDGROUPS_CLEAR_SELECTION', }) -export const addUserToGroups = (user: User, groups: Group[]) => - ({ - type: 'DMS_USERSANDGROUPS_ADD_USER_TO_GROUPS', - inject: async options => { - const currentState = options.getState() - const repository = options.getInjectable(Repository) as Repository - options.dispatch( - startLoading( - currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', - ), - ) - try { - const add = groups.map(async group => { - return await repository.security.addMembers(group.Id, [user.Id]) - }) - await Promise.all(add) - } catch (error) { - options.dispatch(setError(error)) - } finally { - options.dispatch(finishLoading()) - options.dispatch(loadUser(user.Id)) - const comparedList: Group[] = arrayComparer(groups, currentState.dms.usersAndGroups.user.memberships.d.results) - options.dispatch(updateGroupList({ d: { __count: comparedList.length, results: comparedList } })) - } - }, - } as InjectableAction & { odataOptions: ODataParams }) +export const addUserToGroups = (user: User, groups: Group[]) => ({ + type: 'DMS_USERSANDGROUPS_ADD_USER_TO_GROUPS', + inject: async (options: IInjectableActionCallbackParams) => { + const currentState = options.getState() + const repository = options.getInjectable(Repository) as Repository + options.dispatch( + startLoading( + currentState.dms.usersAndGroups.user.currentUser ? currentState.dms.usersAndGroups.user.currentUser.Id : '', + ), + ) + try { + const add = groups.map(async group => { + return await repository.security.addMembers(group.Id, [user.Id]) + }) + await Promise.all(add) + } catch (error) { + options.dispatch(setError(error)) + } finally { + options.dispatch(finishLoading()) + options.dispatch(loadUser(user.Id)) + const comparedList = arrayComparer(groups, currentState.dms.usersAndGroups.user.memberships.d.results) + options.dispatch(updateGroupList({ d: { __count: comparedList.length, results: comparedList } })) + } + }, +}) export const updateGroupList = (groups: ODataCollectionResponse) => ({ type: 'DMS_USERSANDGROUPS_UPDATE_GROUPS', diff --git a/examples/sn-dms-demo/src/store/usersandgroups/reducers.ts b/examples/sn-dms-demo/src/store/usersandgroups/reducers.ts index 15e5e1c6f..d6dc4d960 100644 --- a/examples/sn-dms-demo/src/store/usersandgroups/reducers.ts +++ b/examples/sn-dms-demo/src/store/usersandgroups/reducers.ts @@ -2,7 +2,7 @@ import { ODataCollectionResponse, ODataParams } from '@sensenet/client-core' import { GenericContent, User } from '@sensenet/default-content-types' import { AnyAction, combineReducers, Reducer } from 'redux' -export const currentUser: Reducer = (state: User | null = null, action: AnyAction) => { +export const currentUser: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_USER': return action.content @@ -12,7 +12,7 @@ export const currentUser: Reducer = (state: User | null = null } export const memberships: Reducer> = ( - state: ODataCollectionResponse = { d: { __count: 0, results: [] } }, + state = { d: { __count: 0, results: [] } }, action: AnyAction, ) => { switch (action.type) { @@ -25,7 +25,7 @@ export const memberships: Reducer> = ( } } -export const error: Reducer = (state: any = null, action: AnyAction) => { +export const error: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_ERROR': return action.error @@ -33,7 +33,7 @@ export const error: Reducer = (state: any = null, action: AnyAction) => { return state } } -export const isLoading: Reducer = (state: boolean = false, action: AnyAction) => { +export const isLoading: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_LOADING': return true @@ -44,7 +44,7 @@ export const isLoading: Reducer = (state: boolean = false, action: AnyA } } -export const isAdmin: Reducer = (state: boolean = false, action: AnyAction) => { +export const isAdmin: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'DMS_USER_ISADMIN': return action.admin @@ -53,7 +53,7 @@ export const isAdmin: Reducer = (state: boolean = false, action: AnyAct } } -export const ancestors: Reducer = (state: GenericContent[] = [], action: AnyAction) => { +export const ancestors: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_ANCESTORS': return action.ancestors @@ -62,7 +62,7 @@ export const ancestors: Reducer = (state: GenericContent[] = [ } } -export const selected: Reducer = (state: GenericContent[] = [], action: AnyAction) => { +export const selected: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_ANCESTORS': return action.ancestors @@ -94,10 +94,7 @@ const defaultOptions = { top: loadChunkSize, } as ODataParams -export const grouplistOptions: Reducer> = ( - state: ODataParams = defaultOptions, - action: AnyAction, -) => { +export const grouplistOptions: Reducer> = (state = defaultOptions, action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_CHILDREN_OPTIONS': return action.odataOptions @@ -106,7 +103,7 @@ export const grouplistOptions: Reducer> = ( } } -export const active: Reducer = (state: GenericContent | null = null, action: AnyAction) => { +export const active: Reducer = (state = null, action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_ACTIVE': return action.active @@ -127,7 +124,7 @@ export const user = combineReducers({ active, }) -export const selectedGroups: Reducer = (state: GenericContent[] = [], action: AnyAction) => { +export const selectedGroups: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SELECT_GROUP': return [...action.groups] @@ -142,7 +139,7 @@ export const selectedGroups: Reducer = (state: GenericContent[ } } -export const all: Reducer = (state: GenericContent[] = [], action: AnyAction) => { +export const all: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SET_GROUPS': return action.groups.d.results @@ -151,7 +148,7 @@ export const all: Reducer = (state: GenericContent[] = [], act } } -export const searchTerm: Reducer = (state: string = '', action: AnyAction) => { +export const searchTerm: Reducer = (state = '', action: AnyAction) => { switch (action.type) { case 'DMS_USERSANDGROUPS_SEARCH_GROUPS': return action.text diff --git a/examples/sn-dms-demo/src/store/workspaces/actions.ts b/examples/sn-dms-demo/src/store/workspaces/actions.ts index f1529c70b..6bde3b112 100644 --- a/examples/sn-dms-demo/src/store/workspaces/actions.ts +++ b/examples/sn-dms-demo/src/store/workspaces/actions.ts @@ -1,28 +1,26 @@ import { Repository } from '@sensenet/client-core' import { User, Workspace } from '@sensenet/default-content-types' -import { AnyAction } from 'redux' -import { InjectableAction } from 'redux-di-middleware' +import { IInjectableActionCallbackParams } from 'redux-di-middleware' import { rootStateType } from '../../store/rootReducer' -export const getWorkspaces = () => - ({ - type: 'GET_WORKSPACES', - inject: async options => { - if (!options.getState().dms.workspaces.isLoading) { - options.dispatch(loadWorkspaces()) - const repository = options.getInjectable(Repository) - const workspaces = await repository.loadCollection({ - path: '/', - oDataOptions: { - query: 'TypeIs:Workspace -TypeIs:Site', - select: ['DisplayName', 'Id', 'Path'], - orderby: [['DisplayName', 'asc']], - }, - }) - options.dispatch(setWorkspaces(workspaces.d.results)) - } - }, - } as InjectableAction) +export const getWorkspaces = () => ({ + type: 'GET_WORKSPACES', + inject: async (options: IInjectableActionCallbackParams) => { + if (!options.getState().dms.workspaces.isLoading) { + options.dispatch(loadWorkspaces()) + const repository = options.getInjectable(Repository) + const workspaces = await repository.loadCollection({ + path: '/', + oDataOptions: { + query: 'TypeIs:Workspace -TypeIs:Site', + select: ['DisplayName', 'Id', 'Path'], + orderby: [['DisplayName', 'asc']], + }, + }) + options.dispatch(setWorkspaces(workspaces.d.results)) + } + }, +}) export const loadWorkspaces = () => ({ type: 'LOAD_WORKSPACES', @@ -33,26 +31,25 @@ export const setWorkspaces = (workspaces: Workspace[]) => ({ workspaces, }) -export const loadFavoriteWorkspaces = (userName: string) => - ({ - type: 'LOAD_FAVORITE_WORKSPACES', - inject: async options => { - if ( - options.getState().dms.workspaces.favorites === null || - options.getState().dms.workspaces.favorites.length === 0 - ) { - const repository = options.getInjectable(Repository) - const favorites = await repository.load({ - idOrPath: `/Root/IMS/Public/${userName}`, - oDataOptions: { - select: 'FollowedWorkspaces', - expand: 'FollowedWorkspaces', - }, - }) - options.dispatch(setFavoriteWorkspaces(favorites.d.FollowedWorkspaces as Workspace[])) - } - }, - } as InjectableAction) +export const loadFavoriteWorkspaces = (userName: string) => ({ + type: 'LOAD_FAVORITE_WORKSPACES', + inject: async (options: IInjectableActionCallbackParams) => { + if ( + options.getState().dms.workspaces.favorites === null || + options.getState().dms.workspaces.favorites.length === 0 + ) { + const repository = options.getInjectable(Repository) + const favorites = await repository.load({ + idOrPath: `/Root/IMS/Public/${userName}`, + oDataOptions: { + select: 'FollowedWorkspaces', + expand: 'FollowedWorkspaces', + }, + }) + options.dispatch(setFavoriteWorkspaces(favorites.d.FollowedWorkspaces as Workspace[])) + } + }, +}) export const setFavoriteWorkspaces = (workspaces: Workspace[]) => ({ type: 'SET_FAVORITE_WORKSPACES', diff --git a/examples/sn-dms-demo/src/store/workspaces/reducers.ts b/examples/sn-dms-demo/src/store/workspaces/reducers.ts index 1e799d0ea..ca268a0f9 100644 --- a/examples/sn-dms-demo/src/store/workspaces/reducers.ts +++ b/examples/sn-dms-demo/src/store/workspaces/reducers.ts @@ -2,7 +2,7 @@ import { Workspace } from '@sensenet/default-content-types' import { AnyAction, combineReducers, Reducer } from 'redux' import { setFavoriteWorkspaces, setWorkspaces } from './actions' -export const allWorkspaces: Reducer = (state: Workspace[] = [], action: AnyAction) => { +export const allWorkspaces: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'SET_WORKSPACES': return (action as ReturnType).workspaces @@ -11,7 +11,7 @@ export const allWorkspaces: Reducer = (state: Workspace[] = [], act } } -export const favorites: Reducer = (state: number[] = [], action: AnyAction) => { +export const favorites: Reducer = (state = [], action: AnyAction) => { switch (action.type) { case 'SET_FAVORITE_WORKSPACES': const items = (action as ReturnType).workspaces @@ -25,7 +25,7 @@ export const favorites: Reducer = (state: number[] = [], action: AnyAc } } -export const searchTerm: Reducer = (state: string = '', action: AnyAction) => { +export const searchTerm: Reducer = (state = '', action: AnyAction) => { switch (action.type) { case 'SEARCH_WORKSPACES': return action.text @@ -34,7 +34,7 @@ export const searchTerm: Reducer = (state: string = '', action: AnyActio } } -export const isLoading: Reducer = (state: boolean = false, action: AnyAction) => { +export const isLoading: Reducer = (state = false, action: AnyAction) => { switch (action.type) { case 'LOAD_WORKSPACES': return true diff --git a/examples/sn-dms-demo/webpack.config.js b/examples/sn-dms-demo/webpack.config.js index fd9ec0e15..5d014c9c3 100644 --- a/examples/sn-dms-demo/webpack.config.js +++ b/examples/sn-dms-demo/webpack.config.js @@ -61,7 +61,8 @@ module.exports = { REACT_APP_SERVICE_URL: 'https://dmsservice.demo.sensenet.com', REACT_APP_RECAPTCHA_KEY: '6LcRiy4UAAAAANJjCL8H5c4WG2YeejRuA35e1gcU', }), - // new BundleAnalyzerPlugin() + new webpack.ContextReplacementPlugin(/moment[/\\]locale/, /en-us/), // Bundle only english locale for moment + // new BundleAnalyzerPlugin(), ], module: { rules: [ diff --git a/examples/sn-react-component-docs/.storybook/addons.js b/examples/sn-react-component-docs/.storybook/addons.js index bc63fe242..551a5e5da 100644 --- a/examples/sn-react-component-docs/.storybook/addons.js +++ b/examples/sn-react-component-docs/.storybook/addons.js @@ -1,10 +1,7 @@ -import '@storybook/addon-notes/register'; -import '@storybook/addon-knobs/register'; -import '@storybook/addon-actions/register'; -// import '@storybook/addon-storysource/register'; -import '@storybook/addon-links/register'; -import '@storybook/addon-options/register'; -import '@storybook/addon-a11y/register'; -import '@storybook/addon-jest/register'; -import '@storybook/addon-viewport/register'; -// import 'storybook-addon-material-ui/register'; \ No newline at end of file +import '@storybook/addon-notes/register' +import '@storybook/addon-knobs/register' +import '@storybook/addon-actions/register' +import '@storybook/addon-links/register' +import '@storybook/addon-options/register' +import '@storybook/addon-a11y/register' +import '@storybook/addon-viewport/register' diff --git a/examples/sn-react-component-docs/.storybook/config.js b/examples/sn-react-component-docs/.storybook/config.js index 97e4ca5f5..9d5fc6456 100644 --- a/examples/sn-react-component-docs/.storybook/config.js +++ b/examples/sn-react-component-docs/.storybook/config.js @@ -1,36 +1,39 @@ -import React from "react"; -import { addDecorator, configure, setAddon } from "@storybook/react"; -import { muiTheme } from 'storybook-addon-material-ui' -import { configureViewport, INITIAL_VIEWPORTS } from '@storybook/addon-viewport'; -import { setOptions, withOptions } from '@storybook/addon-options'; -import { setDefaults } from '@storybook/addon-info'; +import React from 'react' +import { addDecorator, addParameters, configure } from '@storybook/react' +import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport' +import { Global, ThemeProvider, themes, createReset, convert } from '@storybook/theming' +import { withInfo } from '@storybook/addon-info' -setOptions({ - name: 'sensenet React Component Docs', - url: 'https://github.com/sensenet/sn-react-component-docs', - hierarchySeparator: /\/|\./, -}) - -setDefaults({ - header: true, - inline: false, - source: true, -}) +addDecorator( + withInfo({ + header: true, + inline: false, + source: true, + }), +) -setAddon(muiTheme) +addDecorator(storyFn => ( + + + {storyFn()} + +)) -configureViewport({ +addParameters({ + options: { + brandTitle: 'sensenet React Component Docs', + brandUrl: 'https://github.com/sensenet/sn-react-component-docs', + hierarchySeparator: /\/|\./, + }, viewports: { ...INITIAL_VIEWPORTS, }, -}); +}) -// automatically import all files ending in *.stories.js -const req = require.context("../stories", true, /.stories.tsx$/); function loadStories() { - addDecorator(muiTheme()) require('./welcomeStory') - req.keys().forEach(filename => req(filename)); + const req = require.context('../stories', true, /\.stories\.tsx$/) + req.keys().forEach(filename => req(filename)) } -configure(loadStories, module); +configure(loadStories, module) diff --git a/examples/sn-react-component-docs/.storybook/docgen-plugin.js b/examples/sn-react-component-docs/.storybook/docgen-plugin.js new file mode 100644 index 000000000..e880239e7 --- /dev/null +++ b/examples/sn-react-component-docs/.storybook/docgen-plugin.js @@ -0,0 +1,51 @@ +const docGen = require('react-docgen-typescript') +const docGenLoader = require('react-docgen-typescript-loader/dist/generateDocgenCodeBlock.js') +const ts = require('typescript') + +class DocgenPlugin { + apply(compiler) { + compiler.hooks.compilation.tap('DocgenPlugin', compilation => { + compilation.hooks.seal.tap('DocgenPlugin', modules => { + const modulesToProcess = [] + compilation.modules.forEach(module => { + // Skip ignored / external modules + if (!module.built || module.external || !module.rawRequest) { + return + } + if (/\/src.+.tsx/.test(module.request)) { + modulesToProcess.push(module) + } + }) + const tsProgram = ts.createProgram(modulesToProcess.map(v => v.userRequest), { + jsx: ts.JsxEmit.React, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.Latest, + }) + modulesToProcess.forEach(m => processModule(m, tsProgram)) + }) + }) + } +} + +function processModule(module, tsProgram) { + if (!module) { + return + } + const componentDocs = docGen.withDefaultConfig().parseWithProgramProvider(module.userRequest, () => tsProgram) + if (!componentDocs.length) return + let source = module._source._value + source += + '\n' + + docGenLoader + .default({ + filename: module.userRequest, + source: module.userRequest, + componentDocs, + docgenCollectionName: 'STORYBOOK_REACT_CLASSES', + setDisplayName: true, + }) + .substring(module.userRequest.length) + + '\n' + module._source._value = source +} +module.exports = DocgenPlugin diff --git a/examples/sn-react-component-docs/.storybook/webpack.config.js b/examples/sn-react-component-docs/.storybook/webpack.config.js index 8fefca543..83c00b3e0 100644 --- a/examples/sn-react-component-docs/.storybook/webpack.config.js +++ b/examples/sn-react-component-docs/.storybook/webpack.config.js @@ -1,34 +1,15 @@ const path = require('path') -/** - * To extend the default Storybook 3 config, it is necessary to import it from - * the @storybook/react package. Storybook 4 provides the default config as the - * third parameter to this module's exported function. - */ -// const genDefaultConfig = require("@storybook/react/dist/server/config/defaults/webpack.config.js"); - -module.exports = (baseConfig, env, config /* Storybook 4 default config */) => { - // Storybook 3 default config - // const config = genDefaultConfig(baseConfig); - - config.module.rules.push( - { - test: /\.tsx?$/, - exclude: [/\/node_modules\/(?!@sensenet\/d)/], - use: [require.resolve('ts-loader'), require.resolve('react-docgen-typescript-loader')], - }, - { - test: /\.stories\.tsx?$/, - loaders: [{ loader: require.resolve('@storybook/addon-storysource/loader'), options: { parser: 'typescript' } }], - enforce: 'pre', - }, - ) - - if (config.optimization) { - config.optimization.minimize = false - } - - config.resolve.extensions.push('.ts', '.tsx') - - return config +// tslint:disable-next-line: variable-name +const TsConfigWebpackPlugin = require('ts-config-webpack-plugin') +const DocgenPlugin = require('./docgen-plugin') + +const config = { + plugins: [new TsConfigWebpackPlugin(), new DocgenPlugin()], + resolve: { + symlinks: true, + extensions: ['.ts', '.tsx'], + }, } + +module.exports = config diff --git a/examples/sn-react-component-docs/.storybook/welcomeStory.js b/examples/sn-react-component-docs/.storybook/welcomeStory.js index f1c3405e3..35fc4da80 100644 --- a/examples/sn-react-component-docs/.storybook/welcomeStory.js +++ b/examples/sn-react-component-docs/.storybook/welcomeStory.js @@ -1,82 +1,108 @@ import React from 'react' import { storiesOf } from '@storybook/react' -import imageFile from '../assets/img/sensenet-logo.png'; -import previewImg from '../assets/img/welcome/preview.png'; -import notesImg from '../assets/img/welcome/notes.png'; -import knobsImg from '../assets/img/welcome/knobs.png'; +import imageFile from '../assets/img/sensenet-logo.png' +import previewImg from '../assets/img/welcome/preview.png' +import notesImg from '../assets/img/welcome/notes.png' +import knobsImg from '../assets/img/welcome/knobs.png' import sourceImg from '../assets/img/welcome/source.png' import propsImg from '../assets/img/welcome/proptable.png' const image = { - src: imageFile, - alt: 'sensenet', -}; + src: imageFile, + alt: 'sensenet', +} -storiesOf('sensenet', module).add('Introduction', () => ( +storiesOf('sensenet', module).add( + 'Introduction', + () => (
-
- {image.alt} -
-
-

Introduction

- This is the guide for using sensenet, a flexible open-source development platform to deliver web based business applications. -
-
-

What is sensenet

- sensenet is a central repository with an extendable API ready for integration. Enterprise grade security and permission system makes it a perfect core of any content management solution. The platform is highly modularized and ready to build your custom business solution and it also provides the possibility creating one page apps using your favourite JavaScript framework without a steep learning curve. You can find more about sensenet in general [here](/guide/introduction/what-is-sensenet). -
-
-

How to use this docs

- -
    -
  • - 1. Choose a component from the menu in the left column (they are grouped by type but you can also search for them with typing in the filter textbox)
  • -
  • - 2. In the preview frame you can see how the control will actually be rendered (you can also try it).
    - -
  • -
  • - 3. Check the notes tab for a quick overview of the chosen component.
    - -
  • -
  • - 4. Try out how you can customize the component on the knobs tab and check the changes in the preview frame.
    - -
  • -
  • - 5. If you want to use your customized control in your application, click on the show info button to see the info panel where you can copy and paste the source code of the modified control.
    - -
  • -
  • - 6. If you need more information about the properties of the component there's a prop type table on the info panel where you can find the types, default values etc. This table is autogenerated from the code of the related component.
    - -
  • -
-
-
+
+ {image.alt} +
+
+

Introduction

+ + This is the guide for using sensenet, a flexible open-source development platform to deliver web based + business applications. + +
+
+

What is sensenet

+ + sensenet is a central repository with an extendable API ready for + integration. Enterprise grade security and permission system makes it a + perfect core of any content management solution. The platform is highly{' '} + modularized and ready to build your custom business solution and it also provides the + possibility creating one page apps using your favourite JavaScript framework without a steep + learning curve. You can find more about sensenet in general [here](/guide/introduction/what-is-sensenet). + +
+
+

How to use this docs

+ +
    +
  • + 1. Choose a component from the menu in the left column (they are grouped by type but you can also search + for them with typing in the filter textbox) +
  • +
  • + 2. In the preview frame you can see how the control will actually be rendered (you can also try it). +
    + +
  • +
  • + 3. Check the notes tab for a quick overview of the chosen component. +
    + +
  • +
  • + 4. Try out how you can customize the component on the knobs tab and check the changes in the preview + frame. +
    + +
  • +
  • + 5. If you want to use your customized control in your application, click on the show info button to see + the info panel where you can copy and paste the source code of the modified control. +
    + +
  • +
  • + 6. If you need more information about the properties of the component there's a prop type table on the + info panel where you can find the types, default values etc. This table is autogenerated from the code of + the related component. +
    + +
  • +
+
+
-)) + ), + { info: { disable: true } }, +) const styles = { - container: { - fontFamily: '-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Lucida Grande","Arial",sans-serif', - padding: 20, - }, - logoContainer: { - width: 200, - margin: '30px auto 50px' - }, - paragraphContainer: {}, - listItem: { - listStyleType: 'none', - margin: '20px 0', - fontWeight: 'bold', - color: '#666', - }, - img: { - margin: '20px 0', - display: 'block', - } -}; -styles.firstCellContainer = { ...styles.cellContainer, marginRight: 20 }; + container: { + fontFamily: + '-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Lucida Grande","Arial",sans-serif', + padding: 20, + }, + logoContainer: { + width: 200, + margin: '30px auto 50px', + }, + paragraphContainer: {}, + listItem: { + listStyleType: 'none', + margin: '20px 0', + fontWeight: 'bold', + color: '#666', + }, + img: { + margin: '20px 0', + display: 'block', + }, +} +styles.firstCellContainer = { ...styles.cellContainer, marginRight: 20 } diff --git a/examples/sn-react-component-docs/notes/fieldcontrols/FileUpload.md b/examples/sn-react-component-docs/notes/fieldcontrols/FileUpload.md new file mode 100644 index 000000000..44cd66dc1 --- /dev/null +++ b/examples/sn-react-component-docs/notes/fieldcontrols/FileUpload.md @@ -0,0 +1,4 @@ +### FileUpload + +FileUpload is a technical field control that let's you upload a file into a specific folder. Since it is only for uploading, it should be combined with another field control that let's you save the reference(s). It has only *new mode*. +The uploaded file is added to the repository as a ```Content```. Files with extension .jpg, .jpeg, .gif, .png are created as ```Image```, all the other files are created by default with the type ```File```. \ No newline at end of file diff --git a/examples/sn-react-component-docs/notes/pickers/ConnectedToDms.md b/examples/sn-react-component-docs/notes/pickers/ConnectedToDms.md new file mode 100644 index 000000000..7e5fa0437 --- /dev/null +++ b/examples/sn-react-component-docs/notes/pickers/ConnectedToDms.md @@ -0,0 +1,5 @@ +### Connected to repository + +Full blown example connected to real repository. To see this working You need to login to https://dmsservice.demo.sensenet.com. + +This component is a basic list component that you can use to select items from. diff --git a/examples/sn-react-component-docs/package.json b/examples/sn-react-component-docs/package.json index cc1a29048..c77d1aef7 100644 --- a/examples/sn-react-component-docs/package.json +++ b/examples/sn-react-component-docs/package.json @@ -1,23 +1,16 @@ { "private": true, "name": "sn-react-component-docs", - "version": "1.0.2", + "version": "2.0.0", "description": "UI components for building apps upon sensenet implemented in React", "main": "index.js", - "dependencies": { - "@date-io/date-fns": "0.0.2", - "@sensenet/client-core": "^1.4.0", - "@sensenet/client-utils": "^1.4.2", - "@sensenet/controls-react": "^2.3.7", - "@sensenet/default-content-types": "^1.1.2", - "@sensenet/icons-react": "^1.2.5", - "@sensenet/list-controls-react": "^1.3.2", - "@sensenet/query": "^1.1.2", - "@sensenet/redux": "^5.1.4", - "@sensenet/search-react": "^1.2.0", - "react": "^16.6.3", - "react-dom": "^16.6.3", - "react-redux": "^6.0.0" + "scripts": { + "build-storybook": "build-storybook -c .storybook", + "start": "start-storybook --static-dir assets", + "fix": "npm run fix:prettier && npm run fix:tslint", + "fix:prettier": "prettier \"{,!(dist|temp|bundle)/**/}*.{ts,tsx}\" --write", + "fix:tslint": "tslint --fix --project .", + "lint": "tslint --project ." }, "repository": { "type": "git", @@ -37,56 +30,46 @@ "url": "https://github.com/SenseNet/sn-icons-react/issues" }, "homepage": "https://sensenet.com", + "dependencies": { + "@sensenet/client-core": "^1.4.1", + "@sensenet/client-utils": "^1.4.2", + "@sensenet/controls-react": "^2.4.0", + "@sensenet/default-content-types": "^1.1.2", + "@sensenet/icons-react": "^1.2.6", + "@sensenet/list-controls-react": "^1.3.3", + "@sensenet/pickers-react": "^1.0.0", + "@sensenet/query": "^1.1.2", + "@sensenet/redux": "^5.1.5", + "@sensenet/search-react": "^1.2.1", + "react": "^16.8.2", + "react-dom": "^16.8.2" + }, "devDependencies": { - "@babel/core": "^7.1.2", - "@storybook/addon-a11y": "^4.1.6", - "@storybook/addon-actions": "4.1.6", - "@storybook/addon-events": "^4.1.6", - "@storybook/addon-info": "4.1.6", - "@storybook/addon-jest": "^4.1.6", - "@storybook/addon-knobs": "^4.1.6", - "@storybook/addon-links": "^4.1.6", - "@storybook/addon-notes": "^4.1.6", - "@storybook/addon-options": "^4.1.6", - "@storybook/addon-storysource": "^4.1.6", - "@storybook/addon-viewport": "^4.1.6", - "@storybook/addons": "4.1.6", - "@storybook/react": "4.1.6", - "@types/memory-fs": "^0.3.2", - "@types/node": "^10.10.1", - "@types/react": "^16.7.14", - "@types/storybook__addon-actions": "^3.0.3", - "@types/storybook__addon-info": "^3.4.2", - "@types/storybook__react": "^4.0.0", - "babel-core": "^6.26.3", - "babel-loader": "^8.0.4", - "babel-runtime": "^6.26.0", - "date-fns": "^2.0.0-alpha.21", - "html-loader": "^0.5.5", - "markdown-loader": "^4.0.0", - "moment": "^2.22.2", - "raw-loader": "^1.0.0", - "react-docgen-typescript-loader": "^3.0.0", + "@babel/core": "7.3.3", + "@storybook/addon-a11y": "^5.0.0", + "@storybook/addon-actions": "^5.0.0", + "@storybook/addon-events": "^5.0.0", + "@storybook/addon-info": "^5.0.0", + "@storybook/addon-knobs": "^5.0.0", + "@storybook/addon-links": "^5.0.0", + "@storybook/addon-notes": "^5.0.0", + "@storybook/addon-options": "^5.0.0", + "@storybook/addon-storysource": "^5.0.0", + "@storybook/addon-viewport": "^5.0.0", + "@storybook/addons": "^5.0.0", + "@storybook/react": "^5.0.0", + "@types/node": "^11.9.4", + "@types/react": "^16.8.3", + "@types/storybook__addon-actions": "^3.4.2", + "@types/storybook__addon-info": "^4.1.0", + "@types/storybook__addon-options": "^4.0.1", + "@types/storybook__addon-viewport": "^4.1.0", + "@types/storybook__react": "^4.0.1", + "babel-loader": "^8.0.5", + "babel-preset-react-app": "^7.0.1", + "react-docgen-typescript-loader": "^3.0.1", "redux": "^4.0.1", - "storybook-addon-material-ui": "^0.9.0-alpha.17", - "ts-loader": "^5.1.1", - "url-loader": "^1.1.2" - }, - "scripts": { - "commit": "git-cz", - "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook -c .storybook", - "clean": "rimraf storybook-static", - "start": "npm run storybook", - "fix": "npm run fix:prettier && npm run fix:tslint", - "fix:prettier": "prettier \"{,!(dist|temp|bundle)/**/}*.{ts,tsx}\" --write", - "fix:tslint": "tslint --fix --project .", - "lint": "tslint --project .", - "test": "" - }, - "config": { - "commitizen": { - "path": "sensenet-kfi-cz-conventional-changelog" - } + "ts-config-webpack-plugin": "^1.3.1", + "webpack": "^4.29.5" } } diff --git a/examples/sn-react-component-docs/stories/FieldControl.stories.tsx b/examples/sn-react-component-docs/stories/FieldControl.stories.tsx index 8da25454f..cfb2f0a33 100644 --- a/examples/sn-react-component-docs/stories/FieldControl.stories.tsx +++ b/examples/sn-react-component-docs/stories/FieldControl.stories.tsx @@ -1,12 +1,9 @@ -import { checkA11y } from '@storybook/addon-a11y' +import { withA11y } from '@storybook/addon-a11y' import { action } from '@storybook/addon-actions' import { withActions } from '@storybook/addon-actions/dist/preview' -import { withInfo } from '@storybook/addon-info' import { boolean, date, number, select, text, withKnobs } from '@storybook/addon-knobs' -import { withMarkdownNotes } from '@storybook/addon-notes' -import { addDecorator, storiesOf } from '@storybook/react' +import { storiesOf } from '@storybook/react' import React from 'react' -import { muiTheme } from 'storybook-addon-material-ui' import { Repository } from '@sensenet/client-core' import { customSchema } from './ViewControl.stories' @@ -19,6 +16,7 @@ import { DisplayName, DropDownList, FileName, + FileUpload, Name, Number, Password, @@ -31,8 +29,6 @@ import { } from '@sensenet/controls-react/src' import { User } from '@sensenet/default-content-types/src' -addDecorator(muiTheme()) - export const testRepository = new Repository({ repositoryUrl: 'https://dmsservice.demo.sensenet.com', requiredSelect: ['Id', 'Path', 'Name', 'Type', 'ParentId', 'DisplayName'] as any, @@ -60,7 +56,7 @@ const radiobuttongroupNotes = require('../notes/fieldcontrols/RadioButtonGroup.m const datetimepickerNotes = require('../notes/fieldcontrols/DateTimePicker.md') const datepickerNotes = require('../notes/fieldcontrols/DatePicker.md') const timepickerNotes = require('../notes/fieldcontrols/TimePicker.md') -const textaresNotes = require('../notes/fieldcontrols/Textarea.md') +const textareasNotes = require('../notes/fieldcontrols/Textarea.md') const richtextNotes = require('../notes/fieldcontrols/RichTextEditor.md') const nameNotes = require('../notes/fieldcontrols/Name.md') const filenameNotes = require('../notes/fieldcontrols/FileName.md') @@ -68,15 +64,15 @@ const passwordNotes = require('../notes/fieldcontrols/Password.md') const numberNotes = require('../notes/fieldcontrols/Number.md') const tagsInputNotes = require('../notes/fieldcontrols/TagsInput.md') const autocompleteNotes = require('../notes/fieldcontrols/AutoComplete.md') +const fileUploadNotes = require('../notes/fieldcontrols/FileUpload.md') storiesOf('FieldControls.AutoComplete', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(autocompleteNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: autocompleteNotes } }, ) .add( 'edit mode', - withMarkdownNotes(autocompleteNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: autocompleteNotes } }, ) .add( 'browse mode', - withMarkdownNotes(autocompleteNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: autocompleteNotes } }, ) storiesOf('FieldControls.CheckboxGroup', module) .addDecorator(withKnobs) - .addDecorator(checkA11y) - .addDecorator(withInfo()) + .addDecorator(withA11y) .add( 'new mode', - withMarkdownNotes(checkboxgroupNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: checkboxgroupNotes } }, ) .add( 'edit mode', - withMarkdownNotes(checkboxgroupNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: checkboxgroupNotes } }, ) .add( 'browse mode', - withMarkdownNotes(checkboxgroupNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: checkboxgroupNotes } }, ) + storiesOf('FieldControls.DatePicker', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(datepickerNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: datepickerNotes } }, ) .add( 'edit mode', - withMarkdownNotes(datepickerNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: datepickerNotes } }, ) .add( 'browse mode', - withMarkdownNotes(datepickerNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: datepickerNotes } }, ) + storiesOf('FieldControls.DateTimePicker', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(datetimepickerNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: datetimepickerNotes } }, ) .add( 'edit mode', - withMarkdownNotes(datetimepickerNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: datetimepickerNotes } }, ) .add( 'browse mode', - withMarkdownNotes(datetimepickerNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: datetimepickerNotes } }, ) + storiesOf('FieldControls.DisplayName', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(displaynameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: displaynameNotes } }, ) .add( 'edit mode', - withMarkdownNotes(displaynameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: displaynameNotes } }, ) .add( 'browse mode', - withMarkdownNotes(displaynameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: displaynameNotes } }, ) + storiesOf('FieldControls.DropDownList', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(dropdownlistNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: dropdownlistNotes } }, ) .add( 'edit mode', - withMarkdownNotes(dropdownlistNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: dropdownlistNotes } }, ) .add( 'browse mode', - withMarkdownNotes(dropdownlistNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: dropdownlistNotes } }, ) storiesOf('FieldControls.FileName', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(filenameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: filenameNotes } }, ) .add( 'edit mode', - withMarkdownNotes(filenameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: filenameNotes } }, ) .add( 'browse mode', - withMarkdownNotes(filenameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: filenameNotes } }, + ) + +storiesOf('FieldControls.FileUpload', module) + .addDecorator(withKnobs) + .addDecorator(withA11y) + .addDecorator(withActions('change')) + .add( + 'new mode', + () => ( + + ), + { notes: { markdown: fileUploadNotes } }, ) storiesOf('FieldControls.Name', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(nameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: nameNotes } }, ) .add( 'edit mode', - withMarkdownNotes(nameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: nameNotes } }, ) .add( 'browse mode', - withMarkdownNotes(nameNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: nameNotes } }, ) + storiesOf('FieldControls.Number', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode integer', - withMarkdownNotes(numberNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: numberNotes } }, ) .add( 'new mode decimal', - withMarkdownNotes(numberNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: numberNotes } }, ) .add( 'edit mode integer', - withMarkdownNotes(numberNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: numberNotes } }, ) .add( 'edit mode decimal', - withMarkdownNotes(numberNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: numberNotes } }, ) .add( 'browse mode', - withMarkdownNotes(numberNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: numberNotes } }, ) + storiesOf('FieldControls.Password', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(passwordNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: passwordNotes } }, ) .add( 'edit mode', - withMarkdownNotes(passwordNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: passwordNotes } }, ) + storiesOf('FieldControls.RadioButtonGroup', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(passwordNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: radiobuttongroupNotes } }, ) .add( 'edit mode', - withMarkdownNotes(radiobuttongroupNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: radiobuttongroupNotes } }, ) .add( 'browse mode', - withMarkdownNotes(radiobuttongroupNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: radiobuttongroupNotes } }, ) + storiesOf('FieldControls.RichTextEditor', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(richtextNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: richtextNotes } }, ) .add( 'edit mode', - withMarkdownNotes(richtextNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: richtextNotes } }, ) .add( 'browse mode', - withMarkdownNotes(richtextNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: richtextNotes } }, ) + storiesOf('FieldControls.ShortText', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(shorttextNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: shorttextNotes } }, ) .add( 'edit mode', - withMarkdownNotes(shorttextNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: shorttextNotes } }, ) .add( 'browse mode', - withMarkdownNotes(shorttextNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: shorttextNotes } }, ) + storiesOf('FieldControls.TagsInput', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(tagsInputNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: tagsInputNotes } }, ) .add( 'edit mode', - withMarkdownNotes(tagsInputNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: tagsInputNotes } }, ) .add( 'browse mode', - withMarkdownNotes(tagsInputNotes)(() => ( + () => ( - )), + ), + { notes: { markdown: tagsInputNotes } }, ) + storiesOf('FieldControls.Textarea', module) .addDecorator(withKnobs) - .addDecorator(withInfo()) - .addDecorator(checkA11y) + .addDecorator(withA11y) .addDecorator(withActions('change')) .add( 'new mode', - withMarkdownNotes(textaresNotes)(() => ( + () => (