diff --git a/client/package.json b/client/package.json index 24211fa19b..278b44fedd 100644 --- a/client/package.json +++ b/client/package.json @@ -12,14 +12,20 @@ "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", "@geoman-io/leaflet-geoman-free": "^2.14.2", + "@ginkgo-bioworks/react-json-schema-form-builder": "2.8.0", "@json-editor/json-editor": "^2.8.0", "@material-table/core": "^6.1.15", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.60", + "@mui/material": "^5.15.14", "@orbat-mapper/convert-symbology": "^1.0.0", + "@rjsf/bootstrap-4": "^5.18.1", + "@rjsf/core": "^5.17.1", + "@rjsf/validator-ajv8": "^5.17.1", "@storybook/addon-essentials": "^8.0.10", "@storybook/addon-interactions": "^8.0.10", + "@storybook/addon-knobs": "^7.0.2", "@storybook/addon-links": "^8.0.10", "@storybook/addon-webpack5-compiler-swc": "^1.0.2", "@storybook/preview-api": "^8.0.10", @@ -38,6 +44,7 @@ "@types/react-color": "^3.0.4", "@types/react-dom": "^16.9.8", "@types/react-flatpickr": "^3.8.4", + "@types/react-jsonschema-form": "^1.7.13", "@types/react-leaflet": "^2.5.1", "@types/react-maskedinput": "^4.0.4", "@types/react-modal": "^3.12.0", @@ -46,6 +53,7 @@ "@types/react-select": "^3.1.2", "@types/react-test-renderer": "^16.9.2", "@types/reactour": "^1.17.1", + "@types/reactstrap": "^8.7.2", "@types/sass": "^1.43.1", "@types/sortablejs": "^1.10.6", "@types/uniqid": "^5.2.0", @@ -98,6 +106,7 @@ "react-data-table-component": "7.4.5", "react-dom": "^16.12.0", "react-dropzone": "^10.1.5", + "react-error-boundary": "^4.0.13", "react-flatpickr": "^3.10.7", "react-fontawesome": "^1.7.1", "react-leaflet-geoman-v2": "1.0.1", @@ -113,7 +122,7 @@ "react-sortablejs": "^6.1.4", "react-test-renderer": "^16.12.0", "reactour": "^1.15.0", - "reactstrap": "^7.1.0", + "reactstrap": "^9.2.2", "redux": "^4.0.1", "redux-logger": "^3.0.6", "redux-mock-store": "^1.5.3", @@ -138,7 +147,7 @@ "typescript": "^4.8.0", "uniqid": "^5.0.3", "universal-router": "^8.1.0", - "vite": "^4.4.7", + "vite": "^5.2.12", "vite-plugin-babel-macros": "^1.0.6", "vite-plugin-checker": "^0.6.1", "vite-plugin-env-compatible": "^1.1.1", @@ -155,6 +164,7 @@ "@types/react-flatpickr": "^3.8.4" }, "devDependencies": { + "@rjsf/utils": "^5.17.1", "@storybook/addon-actions": "^8.0.10", "@storybook/codemod": "^8.0.10", "@storybook/node-logger": "^8.0.10", diff --git a/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_ActionCreators.js b/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_ActionCreators.js index 3766fb1686..fbf4052eb1 100644 --- a/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_ActionCreators.js +++ b/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_ActionCreators.js @@ -1,11 +1,11 @@ -import * as ActionConstant from 'src/config' +// import * as ActionConstant from 'src/config' -export const setSelectedSchema = (schemaId) => ({ - type: ActionConstant.SET_SELECTED_SCHEMA, - payload: schemaId -}) +// export const setSelectedSchema = (schemaId) => ({ +// type: ActionConstant.SET_SELECTED_SCHEMA, +// payload: schemaId +// }) -export const setPreviewSchema = (schema) => ({ - type: ActionConstant.SET_PREVIEW_SCHEMA, - payload: schema -}) +// export const setPreviewSchema = (schema) => ({ +// type: ActionConstant.SET_PREVIEW_SCHEMA, +// payload: schema +// }) diff --git a/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_Reducer.js b/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_Reducer.js index 87748cb0cd..4d5e1b4f46 100644 --- a/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_Reducer.js +++ b/client/src/ActionsAndReducers/UmpireMenu/umpireMenu_Reducer.js @@ -1,4 +1,4 @@ -import * as ActionConstant from 'src/config' +// import * as ActionConstant from 'src/config' import deepCopy from '../../Helpers/copyStateHelper.js' const initialState = { @@ -10,13 +10,13 @@ export const umpireMenuReducer = (state = initialState, action) => { const newState = deepCopy(state) switch (action.type) { - case ActionConstant.SET_SELECTED_SCHEMA: - newState.selectedSchemaID = action.payload - return newState + // case ActionConstant.SET_SELECTED_SCHEMA: + // newState.selectedSchemaID = action.payload + // return newState - case ActionConstant.SET_PREVIEW_SCHEMA: - newState.previewSchema = action.payload - return newState + // case ActionConstant.SET_PREVIEW_SCHEMA: + // newState.previewSchema = action.payload + // return newState default: return newState diff --git a/client/src/ActionsAndReducers/dbMessageTypes/messageTypes_ActionCreators.ts b/client/src/ActionsAndReducers/dbMessageTypes/messageTypes_ActionCreators.ts deleted file mode 100644 index eba29b1ae5..0000000000 --- a/client/src/ActionsAndReducers/dbMessageTypes/messageTypes_ActionCreators.ts +++ /dev/null @@ -1,160 +0,0 @@ -import * as ActionConstant from 'src/config' -import 'whatwg-fetch' -import check from 'check-types' - -import * as messageTypesApi from '../../api/messageTypes_api' - -import { setCurrentViewFromURI } from '../setCurrentViewFromURI/setCurrentViewURI_ActionCreators' -import { addNotification } from '../Notification/Notification_ActionCreators' -import { MessageTypesActionTypes, MessageTypesDispatch, TemplateBody } from 'src/custom-types' - -const DBMessageSaveStatus = (status: boolean): MessageTypesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_STATUS, - payload: status -}) - -const DBSaveMessageArray = (messages: TemplateBody[]): MessageTypesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_TYPES_SAVED, - payload: messages -}) - -const loadingDBMessageCreate = (isLoading: boolean): MessageTypesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_CREATION_LOADING, - payload: isLoading -}) - -const loadingDBMessageGet = (isLoading: boolean): MessageTypesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_TYPES_GET, - payload: isLoading -}) - -const populatingDb = (isLoading: boolean): MessageTypesActionTypes => ({ - type: ActionConstant.POPULATE_MESSAGE_TYPES_DB, - payload: isLoading -}) - -export const populateMessageTypesDb = () => { - return async (dispatch: MessageTypesDispatch) => { - dispatch(populatingDb(true)) - - await messageTypesApi.populateDb() - - const messages = await messageTypesApi.getAllMessagesFromDb() - - dispatch(DBSaveMessageArray(messages)) - - dispatch(populatingDb(false)) - } -} - -// @ts-ignore -export const createMessageType = (schema) => { - if (!check.object(schema)) throw Error(`createMessageType() requires schema object & NOT. ${schema}`) - - return async (dispatch: MessageTypesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - try { - const result = await messageTypesApi.postNewMessageTypeToDb(schema) - - if (result.err) { - // @ts-ignore - dispatch(addNotification(result.err)) - dispatch(loadingDBMessageCreate(false)) - } - - if (result.ok) { - dispatch(DBMessageSaveStatus(result)) - const messages = await messageTypesApi.getAllMessagesFromDb() - dispatch(DBSaveMessageArray(messages)) - - dispatch(loadingDBMessageCreate(false)) - // @ts-ignore - dispatch(setCurrentViewFromURI(ActionConstant.MESSAGE_TEMPLATE_ROUTE)) - } - } catch (err) { - console.error(err) - } - } -} - -export const duplicateMessageType = (id: string) => { - if (!check.string(id)) throw Error(`duplicateTemplate() requires a string Not. ${id}`) - - return async (dispatch: MessageTypesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - const result = await messageTypesApi.duplicateMessageInDb(id) - - if (result) { - dispatch(DBMessageSaveStatus(true)) - const messages = await messageTypesApi.getAllMessagesFromDb() - dispatch(DBSaveMessageArray(messages)) - } - dispatch(loadingDBMessageCreate(false)) - } -} - -// @ts-ignore -export const updateMessageType = (schema, id: string) => { - if (!check.object(schema)) throw Error(`updateMessageType() requires schema & not ${schema}`) - - return async (dispatch: MessageTypesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - try { - const result = await messageTypesApi.updateMessageInDb(schema, id) - - if (result.err) { - // @ts-ignore - dispatch(addNotification(result.err)) - dispatch(loadingDBMessageCreate(false)) - } - - if (result.ok) { - dispatch(DBMessageSaveStatus(result.ok)) - - const allMessages = await messageTypesApi.getAllMessagesFromDb() - - dispatch(DBSaveMessageArray(allMessages)) - dispatch(loadingDBMessageCreate(false)) - // @ts-ignore - dispatch(setCurrentViewFromURI(ActionConstant.MESSAGE_TEMPLATE_ROUTE)) - } - } catch (e) { - // CREATE ERROR WARNING MESSAGE - dispatch(loadingDBMessageCreate(false)) - alert(e) - } - } -} - -export const deleteMessageType = (messageId: string) => { - if (!check.string(messageId)) throw Error(`deleteMessage() requires a string of id not. ${messageId}`) - - return async (dispatch: MessageTypesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - const result = await messageTypesApi.deleteMessageFromDb(messageId) - - if (result) { - const messages = await messageTypesApi.getAllMessagesFromDb() - dispatch(DBSaveMessageArray(messages)) - } else { - // error action - } - - dispatch(loadingDBMessageCreate(false)) - } -} - -export const getAllMessageTypes = () => { - return async (dispatch: MessageTypesDispatch) => { - dispatch(loadingDBMessageGet(true)) - - const result = await messageTypesApi.getAllMessagesFromDb() - - dispatch(DBSaveMessageArray(result)) - dispatch(loadingDBMessageGet(false)) - } -} diff --git a/client/src/ActionsAndReducers/dbMessageTypes/messageTypes_Reducer.ts b/client/src/ActionsAndReducers/dbMessageTypes/messageTypes_Reducer.ts deleted file mode 100644 index 18a5d932d9..0000000000 --- a/client/src/ActionsAndReducers/dbMessageTypes/messageTypes_Reducer.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as ActionConstant from 'src/config' -import { MessageTypes, MessageTypesActionTypes } from 'src/custom-types' -import copyState from '../../Helpers/copyStateHelper' - -const initialState: MessageTypes = { - isLoading: false, - messages: [], - templatesByKey: {} -} - -export const messageTypesReducer = (state: MessageTypes = initialState, action: MessageTypesActionTypes): MessageTypes => { - const newState = copyState(state) - switch (action.type) { - case ActionConstant.DB_MESSAGE_TYPES_GET: - newState.isLoading = action.payload - return newState - - case ActionConstant.DB_MESSAGE_TYPES_SAVED: - newState.messages = action.payload - newState.templatesByKey = {} - // save templatesByKey for easy access - for (const template of action.payload) { - newState.templatesByKey[template._id] = template - } - return newState - - default: - return newState - } -} diff --git a/client/src/ActionsAndReducers/dbMessages/messages_ActionCreators.ts b/client/src/ActionsAndReducers/dbMessages/messages_ActionCreators.ts deleted file mode 100644 index aca3288437..0000000000 --- a/client/src/ActionsAndReducers/dbMessages/messages_ActionCreators.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import * as ActionConstant from 'src/config' -import 'whatwg-fetch' -import check from 'check-types' - -import * as messagesApi from '../../api/messages_api' - -import { addNotification } from '../Notification/Notification_ActionCreators' -// import { MessageFeedback, MessagesActionTypes, MessagesDispatch, RequestForInformation } from 'src/custom-types' -import { MessageFeedback, MessagesActionTypes, MessagesDispatch, RequestForInformation } from 'src/custom-types' - -const DBMessageSaveStatus = (status: string): MessagesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_STATUS, - payload: status -}) - -const DBSaveMessageArray = (messages: []): MessagesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_SAVED, - payload: messages -}) - -const DBSaveMessagePreview = (message: MessageFeedback): MessagesActionTypes => ({ - type: ActionConstant.DB_RETURNED_MESSAGE, - payload: message -}) - -const loadingDBMessageCreate = (isLoading: boolean): MessagesActionTypes => ({ - type: ActionConstant.DB_MESSAGE_CREATION_LOADING, - isLoading -}) - -const loadingDBMessageGet = (isLoading: boolean): MessagesActionTypes => ({ - type: ActionConstant.DB_MESSAGES_GET, - isLoading -}) - -export const resetMessagePreview = (): MessagesActionTypes => ({ - type: ActionConstant.RESET_MESSAGE_PREVIEW -}) - -export const createMessage = (message: RequestForInformation, schema: {}) => { - if (!check.object(message)) throw Error(`createMessageType() requires object with message, from & to NOT. ${message}`) - - return async (dispatch: MessagesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - try { - const result = await messagesApi.addMessage(message, schema) - - if (result.err) { - dispatch(addNotification(result.err, 'warning')) - } - - if (result.ok) { - dispatch(DBMessageSaveStatus(result)) - const messages = await messagesApi.getAllMessagesFromDb() - dispatch(DBSaveMessageArray(messages)) - dispatch(loadingDBMessageCreate(false)) - } - } catch (e) { - dispatch(loadingDBMessageCreate(false)) - alert(e) - } - } -} - -export const duplicateMessage = (messageId: string) => { - if (!check.string(messageId)) throw Error(`duplicateMessage() requires a string Not. ${messageId}`) - - return async (dispatch: MessagesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - const result = await messagesApi.duplicateMessageInDb(messageId) - - if (result) { - dispatch(DBMessageSaveStatus(result)) - const messages = await messagesApi.getAllMessagesFromDb() - dispatch(DBSaveMessageArray(messages)) - } - dispatch(loadingDBMessageCreate(false)) - } -} - -export const updateMessage = (message: RequestForInformation, id: string) => { - if (!check.object(message)) throw Error(`updateMessage() requires object with message, from & to NOT. ${message}`) - - return async (dispatch: MessagesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - try { - const result = await messagesApi.updateMessageInDb(id, message) - - if (result.err) { - dispatch(addNotification(result.err, 'warning')) - } - - if (result.ok) { - dispatch(DBMessageSaveStatus(result)) - - const responses = await Promise.all([messagesApi.getAllMessagesFromDb(), messagesApi.getMessage(result.id)]) - const [messages, message] = [...responses] - - dispatch(DBSaveMessagePreview(message)) - dispatch(DBSaveMessageArray(messages)) - dispatch(loadingDBMessageCreate(false)) - } - } catch (e) { - // CREATE ERROR WARNING MESSAGE - dispatch(loadingDBMessageCreate(false)) - alert(e) - } - } -} - -export const deleteMessage = (messageId: string) => { - if (!check.string(messageId)) throw Error(`duplicateMessage() requires a string Not. ${messageId}`) - - return async (dispatch: MessagesDispatch) => { - dispatch(loadingDBMessageCreate(true)) - - const result = await messagesApi.deleteMessageFromDb(messageId) - - if (result) { - const messages = await messagesApi.getAllMessagesFromDb() - dispatch(DBSaveMessageArray(messages)) - dispatch(resetMessagePreview()) - } else { - // error action - } - - dispatch(loadingDBMessageCreate(false)) - } -} - -export const getSingleMessage = (id: string) => { - if (!check.string(id)) throw Error('duplicateMessage() requires a string id..') - - return async (dispatch: MessagesDispatch) => { - dispatch(loadingDBMessageGet(true)) - - const result = await messagesApi.getMessage(id) - - dispatch(DBSaveMessagePreview(result)) - dispatch(loadingDBMessageGet(false)) - } -} - -export const getAllMessages = () => { - return async (dispatch: MessagesDispatch) => { - dispatch(loadingDBMessageGet(true)) - - const result = await messagesApi.getAllMessagesFromDb() - - dispatch(DBSaveMessageArray(result)) - dispatch(loadingDBMessageGet(false)) - } -} diff --git a/client/src/ActionsAndReducers/dbMessages/messages_Reducer.ts b/client/src/ActionsAndReducers/dbMessages/messages_Reducer.ts deleted file mode 100644 index c81556159e..0000000000 --- a/client/src/ActionsAndReducers/dbMessages/messages_Reducer.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as ActionConstant from 'src/config' -import { MessagesActionTypes } from 'src/custom-types' -import copyState from '../../Helpers/copyStateHelper.js' - -const initialState = { - isLoading: false, - messages: [], - messagePreviewId: '', - messagePreview: {} -} - -export const messagesReducer = (state = initialState, action: MessagesActionTypes) => { - // redux works on the principle of keeping your data immutable. - // directly editing the state will cause an error, it leaves immutability up to you, - // the simplest solution with smallest footprint I've found so far is to use this deepCopy helper I found online. - const newState = copyState(state) - - switch (action.type) { - case ActionConstant.DB_MESSAGES_GET: - newState.isLoading = action.isLoading - return newState - - case ActionConstant.DB_MESSAGE_SAVED: - newState.messages = action.payload - return newState - - case ActionConstant.DB_RETURNED_MESSAGE: - newState.messagePreviewId = action.payload._id - newState.messagePreview = action.payload - return newState - - case ActionConstant.RESET_MESSAGE_PREVIEW: - newState.messagePreviewId = '' - newState.messagePreview = {} - return newState - - default: - return newState - } -} diff --git a/client/src/ActionsAndReducers/dbWargames/helpers/wargamesHandlers.ts b/client/src/ActionsAndReducers/dbWargames/helpers/wargamesHandlers.ts index b7e905c011..79f117d47b 100644 --- a/client/src/ActionsAndReducers/dbWargames/helpers/wargamesHandlers.ts +++ b/client/src/ActionsAndReducers/dbWargames/helpers/wargamesHandlers.ts @@ -1,4 +1,5 @@ -import { WargamesState, WargameRevision, Wargame, ChannelTypes, ForceData, ParticipantTypes, Role, WargameDataChange } from 'src/custom-types' + +import { WargamesState, WargameRevision, Wargame, ChannelTypes, ForceData, ParticipantTypes, Role, WargameDataChange, TemplateBody } from 'src/custom-types' import { channelTemplate, serverPath, forceTemplate } from 'src/config' import uniqId from 'uniqid' @@ -79,6 +80,11 @@ export const handleSetSelectedForce = (newState: WargamesState, payload: { name: newState.data.forces.selectedForce = payload as ForceData } +// Sets the selected template in the global state. +export const handleSetSelectedTemplate = (newState: WargamesState, payload: { title: string, _id: string }) => { + newState.data.templates.selectedTemplate = payload as TemplateBody +} + // Adds a new channel to the wargame in the specified tab. export const handleAddNewChannel = (newState: WargamesState, payload: { name: string, uniqid: string }, tab: string) => { const newChannel = channelTemplate diff --git a/client/src/ActionsAndReducers/dbWargames/wargames_ActionCreators.ts b/client/src/ActionsAndReducers/dbWargames/wargames_ActionCreators.ts index 75b57b95eb..9c3ccb8168 100644 --- a/client/src/ActionsAndReducers/dbWargames/wargames_ActionCreators.ts +++ b/client/src/ActionsAndReducers/dbWargames/wargames_ActionCreators.ts @@ -14,6 +14,7 @@ import { WargameDispatch, WargameOverview, WargameRevision, + TemplateBody, WargameDataChange, WargameList, Message @@ -47,6 +48,11 @@ export const setSelectedForce = (selectedForce: { name: string, uniqid: string, payload: selectedForce }) +export const setSelectedTemplate = (selectedTemplate: { title: string, _id: string }): WargameActionTypes => ({ + type: ActionConstant.SET_SELECTED_TEMPLATE, + payload: selectedTemplate +}) + export const addNewChannel = (data: { name: string, uniqid: string }): WargameActionTypes => ({ type: ActionConstant.ADD_NEW_CHANNEL, payload: data @@ -417,3 +423,35 @@ export const updateWargameVisible = (dbName: string) => { dispatch(saveAllWargameNames(games)) } } + +export const duplicateTemplate = (dbName: string, temlete: string) => { + return async (dispatch: WargameDispatch) => { + const wargame = await wargamesApi.duplicateTemplate(dbName, temlete) + + dispatch(setCurrentWargame(wargame)) + dispatch(addNotification('Template is duplicated.', 'success')) + } +} + +export const deleteSelectedTemplate = (dbName: string, templateId: string) => { + return async (dispatch: WargameDispatch) => { + const wargame = await wargamesApi.deleteTemplate(dbName, templateId) + + dispatch(setCurrentWargame(wargame)) + + dispatch(addNotification('Force deleted.', 'warning')) + } +} + +export const saveTemplate = (dbName: string, data: TemplateBody) => { + return async (dispatch: WargameDispatch) => { + const wargame = await wargamesApi.saveTemplate(dbName, data) + + dispatch(setCurrentWargame(wargame)) + + dispatch(setTabSaved()) + dispatch(setSelectedTemplate({ title: data.title, _id: data._id })) + + dispatch(addNotification('Template is saved.', 'success')) + } +} diff --git a/client/src/ActionsAndReducers/dbWargames/wargames_Reducer.ts b/client/src/ActionsAndReducers/dbWargames/wargames_Reducer.ts index a9f3cd2ff5..387462483b 100644 --- a/client/src/ActionsAndReducers/dbWargames/wargames_Reducer.ts +++ b/client/src/ActionsAndReducers/dbWargames/wargames_Reducer.ts @@ -5,7 +5,7 @@ import * as wargamesHandlers from './helpers/wargamesHandlers' import { dbDefaultSettings } from 'src/config' import { WargamesState, WargameActionTypes, AllWargameNameSaved, SetCurrentWargame, SetGameSetupData, setWargameTitle, AddNewRecipient, RemoveRecipient, ActionHandler, - SetCurrentGameSetupTab, AddNewForce, SetForceColor, AddNewChannel, SetSelectedChannel, AddRoleToForce, RemoveRoleFromForce, AddIcon, SetSelectedForce + SetCurrentGameSetupTab, AddNewForce, SetForceColor, AddNewChannel, SetSelectedChannel, AddRoleToForce, RemoveRoleFromForce, AddIcon, SetSelectedForce, SetSelectedTemplate } from 'src/custom-types' const initialState: WargamesState = { @@ -35,6 +35,7 @@ const handlers: Record = { [ActionConstant.ADD_NEW_FORCE]: (newState, action, tab) => wargamesHandlers.handleAddNewForce(newState, (action as AddNewForce).payload, tab), [ActionConstant.SET_FORCE_COLOR]: (newState, action, tab) => wargamesHandlers.handleSetForceColor(newState, (action as SetForceColor).payload, tab), [ActionConstant.SET_SELECTED_FORCE]: (newState, action) => wargamesHandlers.handleSetSelectedForce(newState, (action as SetSelectedForce).payload), + [ActionConstant.SET_SELECTED_TEMPLATE]: (newState, action) => wargamesHandlers.handleSetSelectedTemplate(newState, (action as SetSelectedTemplate).payload), [ActionConstant.ADD_NEW_CHANNEL]: (newState, action, tab) => wargamesHandlers.handleAddNewChannel(newState, (action as AddNewChannel).payload, tab), [ActionConstant.SET_SELECTED_CHANNEL]: (newState, action, tab) => wargamesHandlers.handleSetSelectedChannel(newState, (action as SetSelectedChannel).payload, tab), [ActionConstant.DELETE_SELECTED_CHANNEL]: (newState, action, tab) => wargamesHandlers.handleDeleteSelectedChannel(newState, (action as SetSelectedChannel).payload, tab), diff --git a/client/src/ActionsAndReducers/loadingDb_Reducer.js b/client/src/ActionsAndReducers/loadingDb_Reducer.js index 684bc2ffb0..849d587d8f 100644 --- a/client/src/ActionsAndReducers/loadingDb_Reducer.js +++ b/client/src/ActionsAndReducers/loadingDb_Reducer.js @@ -2,18 +2,13 @@ import * as ActionConstant from 'src/config' import copyState from '../Helpers/copyStateHelper' const initialState = { - loadingMessageTypes: true, loadingWargames: true } export const loadingDbReducer = (state = initialState, action) => { const newState = copyState(state) - + switch (action.type) { - case ActionConstant.POPULATE_MESSAGE_TYPES_DB: - newState.loadingMessageTypes = action.isLoading - return newState - case ActionConstant.POPULATE_WARGAMES_DB: newState.loadingWargames = action.isLoading return newState diff --git a/client/src/ActionsAndReducers/playerUi/helpers/handleWargameMessagesChange.ts b/client/src/ActionsAndReducers/playerUi/helpers/handleWargameMessagesChange.ts index 3ad651e7ca..916be06dd3 100644 --- a/client/src/ActionsAndReducers/playerUi/helpers/handleWargameMessagesChange.ts +++ b/client/src/ActionsAndReducers/playerUi/helpers/handleWargameMessagesChange.ts @@ -56,7 +56,7 @@ const openMessageChange = (message: MessageChannel, id: string): { message: Mess let changed = false if (message._id === id) { changed = true - message.isOpen = true + message.isOpen = !message.isOpen message.hasBeenRead = true } return { message, changed } diff --git a/client/src/ActionsAndReducers/playerUi/playerUi_Reducer.ts b/client/src/ActionsAndReducers/playerUi/playerUi_Reducer.ts index e3f456ef5e..3d2e30e78b 100644 --- a/client/src/ActionsAndReducers/playerUi/playerUi_Reducer.ts +++ b/client/src/ActionsAndReducers/playerUi/playerUi_Reducer.ts @@ -77,7 +77,7 @@ const actionHandlers: Record = { [SET_ALL_TEMPLATES_PLAYERUI]: (_newState, action) => { // note: Uncomment this line to update the 'allTemplatesByKey' in the state with the payload from the action // _newState.allTemplatesByKey = (action as SetAllTemplatesAction).payload - + console.warn('ignoring templates from message types database', (action as SetAllTemplatesAction).payload) }, [SHOW_HIDE_OBJECTIVES]: newState => { diff --git a/client/src/Components/ChatChannel.tsx b/client/src/Components/ChatChannel.tsx index 9cad13e867..f44c796900 100644 --- a/client/src/Components/ChatChannel.tsx +++ b/client/src/Components/ChatChannel.tsx @@ -16,7 +16,7 @@ import 'src/themes/App.scss' import { usePlayerUiDispatch, usePlayerUiState } from '../Store/PlayerUi' import { MessageReadInteraction, MessageSentInteraction, MessageUnReadInteraction } from 'src/custom-types/player-log' -import { MESSAGE_UNREAD_INTERACTION, MESSAGE_SENT_INTERACTION, MESSAGE_READ_INTERACTION } from 'src/config' +import { MESSAGE_UNREAD_INTERACTION, MESSAGE_SENT_INTERACTION, MESSAGE_READ_INTERACTION, CUSTOM_MESSAGE } from 'src/config' const ChatChannel: React.FC<{ channelId: string, isCustomChannel?: boolean }> = ({ channelId, isCustomChannel }) => { const state = usePlayerUiState() @@ -42,12 +42,12 @@ const ChatChannel: React.FC<{ channelId: string, isCustomChannel?: boolean }> = setChannelTabClass(`tab-content-${channelClassName}`) }, []) - const messageHandler = (details: MessageDetails, message: any, templeteId: string, messageType: TypeOfCustomMessage): void => { + const messageHandler = (details: MessageDetails, message: any, templateId: string, messageType: TypeOfCustomMessage): void => { const sendMessage: MessageSentInteraction = { aType: MESSAGE_SENT_INTERACTION } saveNewActivityTimeMessage(details.from.roleId, sendMessage, state.currentWargame)(dispatch) - saveMessage(state.currentWargame, details, message, templeteId, messageType)() + saveMessage(state.currentWargame, details, message, templateId, messageType)() } const markAllAsReadLocal = (): void => { @@ -97,7 +97,7 @@ const ChatChannel: React.FC<{ channelId: string, isCustomChannel?: boolean }> = _id: detail._id || 'na' } saveNewActivityTimeMessage(coreMessage.details.from.roleId, readMessage, state.currentWargame)(dispatch) - + playerUiDispatch(openMessage(channelId, detail)) } @@ -105,6 +105,9 @@ const ChatChannel: React.FC<{ channelId: string, isCustomChannel?: boolean }> = if (message._id) { message.hasBeenRead = false } + if (message.messageType === CUSTOM_MESSAGE) { + message.isOpen = false + } // since this is a message, we know it must come from CoreMessage const coreMessage = message as any as CoreMessage const unreadMessage: MessageUnReadInteraction = { @@ -129,7 +132,7 @@ const ChatChannel: React.FC<{ channelId: string, isCustomChannel?: boolean }> = return clearUnsentMessage(state.currentWargame, selectedForceId, state.selectedRole, channelId, removeType) }) } - + console.log('isCustomChannel', isCustomChannel) return (
{ diff --git a/client/src/Components/Modals/DeleteConfirmation.tsx b/client/src/Components/Modals/DeleteConfirmation.tsx index c9ffb0fe25..3e73aa5797 100644 --- a/client/src/Components/Modals/DeleteConfirmation.tsx +++ b/client/src/Components/Modals/DeleteConfirmation.tsx @@ -7,7 +7,8 @@ import { clearWargames, deleteSelectedChannel, deleteSelectedForce, - deleteSelectedRole + deleteSelectedRole, + deleteSelectedTemplate } from '../../ActionsAndReducers/dbWargames/wargames_ActionCreators' import { modalAction } from '../../ActionsAndReducers/Modal/Modal_ActionCreators' @@ -31,7 +32,7 @@ const DeleteModal: React.FC = () => { const onDelete = () => { const curTab = wargame.currentTab - + switch (type) { case 'force': { if (curTab && wargame.currentWargame) { @@ -45,6 +46,10 @@ const DeleteModal: React.FC = () => { if (wargame.currentWargame) dispatch(deleteSelectedChannel(wargame.currentWargame, data as string)) break } + case 'template': { + if (wargame.currentWargame) dispatch(deleteSelectedTemplate(wargame.currentWargame, data as string)) + break + } case 'role': { if (wargame.currentWargame) dispatch(deleteSelectedRole(wargame.currentWargame, data as RoleType)) break diff --git a/client/src/Components/Modals/DeleteModal.tsx b/client/src/Components/Modals/DeleteModal.tsx deleted file mode 100644 index c86ad723e5..0000000000 --- a/client/src/Components/Modals/DeleteModal.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react' -import Confirm from '../local/atoms/confirm' -import { RootState } from 'src/custom-types' -import 'src/themes/App.scss' -import { useDispatch, useSelector } from 'react-redux' -import { deleteMessage } from '../../ActionsAndReducers/dbMessages/messages_ActionCreators' -import { deleteMessageType } from '../../ActionsAndReducers/dbMessageTypes/messageTypes_ActionCreators' -import { modalAction } from '../../ActionsAndReducers/Modal/Modal_ActionCreators' -import { setPreviewSchema, setSelectedSchema } from '../../ActionsAndReducers/UmpireMenu/umpireMenu_ActionCreators' -import { MESSAGE_TEMPLATE_ROUTE } from 'src/config' - -const DeleteModal: React.FC = (): JSX.Element => { - const dispatch = useDispatch() - const currentModal = useSelector((state: RootState) => state.currentModal) - const messages = useSelector((state: RootState) => state.messages) - const umpireMenu = useSelector((state: RootState) => state.umpireMenu) - const currentViewURI = useSelector((state: RootState) => state.currentViewURI) - - const onHideModal = () => { - dispatch(modalAction.close()) - } - - const onDeleteMessage = () => { - if (currentViewURI === MESSAGE_TEMPLATE_ROUTE) { - dispatch(deleteMessageType(umpireMenu.selectedSchemaID)) - dispatch(setPreviewSchema('')) - dispatch(setSelectedSchema('')) - } else { - dispatch(deleteMessage(messages.messagePreviewId)) - } - dispatch(modalAction.close()) - } - - if (!currentModal.open) return <> - - return ( - - ) -} - -export default DeleteModal diff --git a/client/src/Components/Modals/ModalSwitch/ModalSwitchAdmin.tsx b/client/src/Components/Modals/ModalSwitch/ModalSwitchAdmin.tsx index 7bce492986..b6368886aa 100644 --- a/client/src/Components/Modals/ModalSwitch/ModalSwitchAdmin.tsx +++ b/client/src/Components/Modals/ModalSwitch/ModalSwitchAdmin.tsx @@ -1,6 +1,6 @@ import React, { Component } from 'react' import { connect } from 'react-redux' -import DeleteModal from '../DeleteModal' +// import DeleteModal from '../DeleteModal' import DeleteWargameModal from '../DeleteWargameModal' import UnsavedForceModal from '../UnsavedForceModal' import UnsavedChannelModal from '../UnsavedChannelModal' @@ -15,10 +15,6 @@ class ModalSwitchAdmin extends Component<{ currentModal: CurrentModal }> { render () { let modal switch (this.props.currentModal.modal) { - case 'delete': - modal = - break - case 'deleteWargame': modal = break diff --git a/client/src/Components/Modals/UnsavedForceModal.tsx b/client/src/Components/Modals/UnsavedForceModal.tsx index 9f380141a8..023c5c4a2d 100644 --- a/client/src/Components/Modals/UnsavedForceModal.tsx +++ b/client/src/Components/Modals/UnsavedForceModal.tsx @@ -57,26 +57,6 @@ const UnsavedForceModal: React.FC = () => { children: 'Don\'t save' }] - // const actions = [{ - // onClick: () => setView(`${MESSAGE_CREATOR_BASE_ROUTE}${EDIT_TEMPLATE_ROUTE}`), - // color: 'secondary', - // fullWidth: true, - // icon: 'edit', - // children: 'Edit' - // }, { - // onClick: duplicateTemplate, - // color: 'secondary', - // fullWidth: true, - // icon: 'copy', - // children: 'Duplicate' - // }, { - // onClick: deleteTemplate, - // color: 'secondary', - // fullWidth: true, - // icon: 'delete', - // children: 'Delete' - // }] - return (
diff --git a/client/src/Components/Router/RouterDashboard.jsx b/client/src/Components/Router/RouterDashboard.jsx index f40d733cab..3eeb068ef9 100644 --- a/client/src/Components/Router/RouterDashboard.jsx +++ b/client/src/Components/Router/RouterDashboard.jsx @@ -1,20 +1,12 @@ import { Component } from 'react' import { connect } from 'react-redux' import UniversalRouter from 'universal-router' -import { - DEMO_ROUTE, - ADMIN_ROUTE, - CREATE_TEMPLATE_ROUTE, - EDIT_TEMPLATE_ROUTE, EXPORT_ROUTE, EXPORT_MESSAGES_SUBROUTE, EXPORT_FORCES_SUBROUTE, EXPORT_PRINT_SUBROUTE, GAME_SETUP_ROUTE, - MESSAGE_CREATOR_BASE_ROUTE, MESSAGE_TEMPLATE_ROUTE, WELCOME_SCREEN_EDIT_ROUTE -} from 'src/config' +import { DEMO_ROUTE, ADMIN_ROUTE, EXPORT_ROUTE, EXPORT_MESSAGES_SUBROUTE, EXPORT_FORCES_SUBROUTE, EXPORT_PRINT_SUBROUTE, GAME_SETUP_ROUTE, WELCOME_SCREEN_EDIT_ROUTE } from 'src/config' import GameDesignerInterface from '../../Views/GameDesignerInterface' -import MessageTemplates from '../../Views/MessageTemplates' -import EditTemplate from '../../Views/EditTemplate' -import CreateTemplate from '../../Views/CreateTemplate' import AdminGameSetup from '../../Views/GameSetupNew' // import GameSetup from '../../Views/GameSetup' import ExportMessages from '../../Views/ExportMessages/ExportMessages' +import 'src/themes/App.scss' import ExportForces from '../../Views/ExportForces' import ExportPrint from '../../Views/ExportPrint' import EditWelcomeScreen from '../../Views/EditWelcomeScreen' @@ -24,15 +16,6 @@ import { setCurrentViewFromURI } from '../../ActionsAndReducers/setCurrentViewFr const routes = [ { path: DEMO_ROUTE, action: () => }, { path: ADMIN_ROUTE, action: () => }, - { path: MESSAGE_TEMPLATE_ROUTE, action: () => }, - { - path: MESSAGE_CREATOR_BASE_ROUTE, - children: - [ - { path: CREATE_TEMPLATE_ROUTE, action: () => }, - { path: EDIT_TEMPLATE_ROUTE, action: () => } - ] - }, { path: EXPORT_ROUTE, children: @@ -70,7 +53,8 @@ class RouterDashboard extends Component { }) } } - + + // ModalSwitchAdmin -na anum delet switch admin page-um. render () { return ( <> diff --git a/client/src/Components/SidebarAdmin.jsx b/client/src/Components/SidebarAdmin.jsx index 9e37fdf2ab..37be6f36d1 100644 --- a/client/src/Components/SidebarAdmin.jsx +++ b/client/src/Components/SidebarAdmin.jsx @@ -1,15 +1,15 @@ + import { ADMIN_ROUTE, - MESSAGE_TEMPLATE_ROUTE, WELCOME_SCREEN_EDIT_ROUTE, REST_API_ROUTE } from 'src/config' + import Link from './Link' export default (props) => { const menus = { [ADMIN_ROUTE]: 'Games', - [MESSAGE_TEMPLATE_ROUTE]: 'Message Templates', [WELCOME_SCREEN_EDIT_ROUTE]: 'Welcome Screen', [REST_API_ROUTE]: 'REST API' } diff --git a/client/src/Components/jsonSchemaEditor/data.json b/client/src/Components/jsonSchemaEditor/data.json deleted file mode 100644 index f1d3a42270..0000000000 --- a/client/src/Components/jsonSchemaEditor/data.json +++ /dev/null @@ -1,865 +0,0 @@ -{ - "$ref": "#/definitions/object", - "title": "object type", - "definitions": { - "string": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "title" - ], - "additionalProperties": false, - "title": "string type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "string" - }, - "format": { - "type": "string", - "enum": [ - "color", - "date", - "datetime", - "datetime-local", - "email", - "month", - "number", - "range", - "tel", - "text", - "textarea", - "time", - "url", - "week", - "json", - "sql", - "xml", - "yaml" - ] - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "string" - ] - }, - "enum": { - "format": "table", - "items": { - "title": "alternative", - "type": "string" - }, - "type": "array" - }, - "description": { - "type": "string" - } - } - }, - "null": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "title" - ], - "additionalProperties": false, - "title": "null type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "null" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "null" - ] - }, - "description": { - "type": "string" - } - } - }, - "array": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "items", - "title" - ], - "additionalProperties": false, - "title": "array type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "array" - }, - "format": { - "default": "table", - "type": "string", - "enum": [ - "array", - "table", - "tabs", - "tabs-top", - "checkbox", - "select", - "categories" - ] - }, - "items": { - "$ref": "#/definitions/schema" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "array" - ] - }, - "description": { - "type": "string" - } - } - }, - "schema": { - "default": { - "type": "object" - }, - "oneOf": [ - { - "$ref": "#/definitions/null", - "title": "null type" - }, - { - "$ref": "#/definitions/boolean", - "title": "boolean type" - }, - { - "$ref": "#/definitions/string", - "title": "string type" - }, - { - "$ref": "#/definitions/integer", - "title": "integer type" - }, - { - "$ref": "#/definitions/number", - "title": "number type" - }, - { - "$ref": "#/definitions/object", - "title": "object type" - }, - { - "$ref": "#/definitions/array", - "title": "array type" - } - ], - "options": { - "keep_oneof_values": false - }, - "type": "object" - }, - "boolean": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "title" - ], - "additionalProperties": false, - "title": "boolean type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "boolean" - }, - "format": { - "type": "string", - "enum": [ - "select", - "checkbox" - ] - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "boolean" - ] - }, - "description": { - "type": "string" - } - } - }, - "object": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "properties", - "title" - ], - "additionalProperties": false, - "title": "object type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "object" - }, - "format": { - "type": "string", - "enum": [ - "grid" - ] - }, - "required": { - "uniqueItems": true, - "format": "table", - "items": { - "title": "property", - "type": "string" - }, - "type": "array" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "object" - ] - }, - "description": { - "type": "string" - }, - "properties": { - "type": "object", - "patternProperties": { - ".*": { - "$ref": "#/definitions/propertySchema" - } - } - } - } - }, - "number": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "title" - ], - "additionalProperties": false, - "title": "number type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "maximum": { - "type": "number" - }, - "default": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "number" - ] - }, - "enum": { - "format": "table", - "items": { - "title": "alternative", - "type": "number" - }, - "type": "array" - }, - "description": { - "type": "string" - } - } - }, - "integer": { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "title" - ], - "additionalProperties": false, - "title": "integer type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "maximum": { - "type": "integer" - }, - "default": { - "type": "integer" - }, - "minimum": { - "type": "integer" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "title": { - "type": "string", - "propertyOrder": 1 - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "integer" - ] - }, - "enum": { - "format": "table", - "items": { - "title": "alternative", - "type": "integer" - }, - "type": "array" - }, - "description": { - "type": "string" - } - } - }, - "propertySchema": { - "default": { - "type": "object" - }, - "oneOf": [ - { - "required": [ - "type" - ], - "defaultProperties": [ - "type" - ], - "additionalProperties": false, - "title": "null type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "null" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "null" - ] - }, - "description": { - "type": "string" - } - } - }, - { - "required": [ - "type" - ], - "defaultProperties": [ - "type" - ], - "additionalProperties": false, - "title": "boolean type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "boolean" - }, - "format": { - "type": "string", - "enum": [ - "select", - "checkbox" - ] - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "boolean" - ] - }, - "description": { - "type": "string" - } - } - }, - { - "required": [ - "type" - ], - "defaultProperties": [ - "type" - ], - "additionalProperties": false, - "title": "string type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "string" - }, - "format": { - "type": "string", - "enum": [ - "color", - "date", - "datetime", - "datetime-local", - "email", - "month", - "number", - "range", - "tel", - "text", - "textarea", - "time", - "url", - "week", - "json", - "sql", - "xml", - "yaml" - ] - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "string" - ] - }, - "enum": { - "format": "table", - "items": { - "title": "alternative", - "type": "string" - }, - "type": "array" - }, - "description": { - "type": "string" - } - } - }, - { - "required": [ - "type" - ], - "defaultProperties": [ - "type" - ], - "additionalProperties": false, - "title": "integer type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "maximum": { - "type": "integer" - }, - "default": { - "type": "integer" - }, - "minimum": { - "type": "integer" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "integer" - ] - }, - "enum": { - "format": "table", - "items": { - "title": "alternative", - "type": "integer" - }, - "type": "array" - }, - "description": { - "type": "string" - } - } - }, - { - "required": [ - "type" - ], - "defaultProperties": [ - "type" - ], - "additionalProperties": false, - "title": "number type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "maximum": { - "type": "number" - }, - "default": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "number" - ] - }, - "enum": { - "format": "table", - "items": { - "title": "alternative", - "type": "number" - }, - "type": "array" - }, - "description": { - "type": "string" - } - } - }, - { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "properties" - ], - "additionalProperties": false, - "title": "object type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "object" - }, - "format": { - "type": "string", - "enum": [ - "grid" - ] - }, - "required": { - "uniqueItems": true, - "format": "table", - "items": { - "title": "property", - "type": "string" - }, - "type": "array" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "object" - ] - }, - "description": { - "type": "string" - }, - "properties": { - "type": "object", - "patternProperties": { - ".*": { - "$ref": "#/definitions/propertySchema" - } - } - } - } - }, - { - "required": [ - "type" - ], - "defaultProperties": [ - "type", - "items" - ], - "additionalProperties": false, - "title": "array type", - "type": "object", - "properties": { - "$ref": { - "format": "uri", - "type": "string" - }, - "default": { - "type": "array" - }, - "format": { - "default": "table", - "type": "string", - "enum": [ - "array", - "table", - "tabs", - "tabs-top", - "checkbox", - "select", - "categories" - ] - }, - "items": { - "$ref": "#/definitions/schema" - }, - "$schema": { - "format": "uri", - "type": "string" - }, - "id": { - "format": "uri", - "type": "string" - }, - "type": { - "options": { - "hidden": true - }, - "type": "string", - "enum": [ - "array" - ] - }, - "description": { - "type": "string" - } - } - } - ], - "options": { - "keep_oneof_values": false - }, - "type": "object" - } - } -} diff --git a/client/src/Components/jsonSchemaEditor/defaultOptions.js b/client/src/Components/jsonSchemaEditor/defaultOptions.js deleted file mode 100644 index 5400334db5..0000000000 --- a/client/src/Components/jsonSchemaEditor/defaultOptions.js +++ /dev/null @@ -1,16 +0,0 @@ -export const booleanOptions = [ - { value: 'required_by_default', label: 'Object properties required by default', selected: false }, - { value: 'display_required_only', label: 'Only show required properties by default', selected: false }, - { value: 'no_additional_properties', label: 'No additional object properties', selected: false }, - { value: 'ajax', label: 'Allow loading Schemas via Ajax', selected: false }, - { value: 'disable_edit_json', label: 'Disable "Edit JSON" buttons' }, - { value: 'disable_collapse', label: 'Disable collapse buttons', selected: false }, - { value: 'disable_properties', label: 'Disable properties buttons', selected: false }, - { value: 'disable_array_add', label: 'Disable array add buttons', selected: false }, - { value: 'disable_array_reorder', label: 'Disable array move buttons', selected: false }, - { value: 'disable_array_delete', label: 'Disable array delete buttons', selected: false }, - { value: 'enable_array_copy', label: 'Add copy buttons to arrays', selected: false }, - { value: 'array_controls_top', label: 'Array controls will be displayed at top of list', selected: false }, - { value: 'disable_array_delete_all_rows', label: 'Disable array delete all rows buttons', selected: false }, - { value: 'disable_array_delete_last_row', label: 'Disable array delete last row buttons', selected: false } -] diff --git a/client/src/Components/jsonSchemaEditor/editorPreview.jsx b/client/src/Components/jsonSchemaEditor/editorPreview.jsx deleted file mode 100644 index 4c39c8e73d..0000000000 --- a/client/src/Components/jsonSchemaEditor/editorPreview.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { Component } from 'react' -import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap' -import { PreviewEditor } from './json-schema-editor/editor' - -class EditorPreview extends Component { - constructor (props, content) { - super(props, content) - - this.previewBox = React.createRef() - this.editorPreview = null - } - - componentDidMount () { - this.editorPreview = new PreviewEditor(this.previewBox) - } - - UNSAFE_componentWillReceiveProps ({ schema }) { - this.editorPreview.destroy() - if (schema !== null) this.editorPreview.updateSchema(schema) - } - - render () { - return ( - - - Editor Preview - - -
- - - The Editor Preview will be updated as you make changes in the Schema Editor - - - ) - } -} - -export default EditorPreview diff --git a/client/src/Components/jsonSchemaEditor/index.jsx b/client/src/Components/jsonSchemaEditor/index.jsx deleted file mode 100644 index 59cd21ea16..0000000000 --- a/client/src/Components/jsonSchemaEditor/index.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { Component } from 'react' -import jsonMetaSchema from './data.json' -import * as JSONEditor from '@json-editor/json-editor' -import Editor from './jsonEditor' -import Preview from './editorPreview' -import '@fortawesome/fontawesome-free/css/all.css' -import { setPreviewSchema } from '../../ActionsAndReducers/UmpireMenu/umpireMenu_ActionCreators' -import { connect } from 'react-redux' - -class SchemaEditor extends Component { - constructor (props, content) { - super(props, content) - - JSONEditor.JSONEditor.defaults.options.iconlib = 'fontawesome5' - JSONEditor.JSONEditor.defaults.options.theme = 'bootstrap4' - this.updateMetaSchema = this.updateMetaSchema.bind(this) - this.updatePreviewSchema = this.updatePreviewSchema.bind(this) - this.updateOptions = this.updateOptions.bind(this) - - this.schemaEditor = null - this.editorPreview = null - - this.defaultValue = { - type: 'object', - properties: { - title: { - type: 'string' - }, - Date: { - type: 'string', - format: 'datetime-local' - }, - Status: { - type: 'string', - enum: [ - 'Minor', - 'Major', - 'Critical' - ] - }, - Description: { - type: 'string', - format: 'textarea' - } - }, - title: 'Machinery Failure', - format: 'grid' - } - - if (this.props.schemaId) { - this.defaultValue = this.props.messageTypes.messages.find((mes) => mes._id === this.props.schemaId).details - } - - this.state = { - metaSchema: jsonMetaSchema, - previewSchema: null, - options: { - layout: JSONEditor.JSONEditor.defaults.options.object_layout, - booleanOptions: {} - } - } - } - - updatePreviewSchema (schema) { - this.setState({ previewSchema: schema }) - - this.props.dispatch(setPreviewSchema(schema)) - } - - updateOptions (options) { - this.setState({ options }) - } - - updateMetaSchema (schema) { - this.setState({ metaschema: schema }) - } - - render () { - return ( - <> -
- -
-
- -
- - ) - } -} - -const mapStateToProps = ({ umpireMenu }) => ({ - umpireMenu -}) - -export default connect(mapStateToProps)(SchemaEditor) diff --git a/client/src/Components/jsonSchemaEditor/json-schema-editor/editor.js b/client/src/Components/jsonSchemaEditor/json-schema-editor/editor.js deleted file mode 100644 index 72ad669ebf..0000000000 --- a/client/src/Components/jsonSchemaEditor/json-schema-editor/editor.js +++ /dev/null @@ -1,206 +0,0 @@ -import JSONEditor from '@json-editor/json-editor' -// -------- Editor class --------------------------------------------------- - -// create a JSON Editor, elementId is the id in which to render the editor -function Editor (elementId) { - this.jsonEditor = null - this.renderZone = typeof elementId === 'string' ? document.getElementById(elementId) : elementId.current -} - -// Destroy editor, and remove it from view -Editor.prototype.destroy = function () { - if (this.jsonEditor) { - this.jsonEditor.destroy() - this.jsonEditor = null - } -} - -// Recreate a new editor based on the given schema and start value -// If schema is undefined, the editor is not recreated, but only destroyed -Editor.prototype.updateSchema = function (schema) { - this.destroy() - this.jsonEditor = new JSONEditor.JSONEditor(this.renderZone, { schema }) -} - -// Validate JSON -Editor.prototype.validate = function () { - return this.jsonEditor.validate() -} - -// Get generated JSON -Editor.prototype.getJSON = function () { - return this.jsonEditor.getValue() -} - -// --------------- PreviewEditor subclass ------------------------------------ - -// create a JSON Editor that doesn't allow specifying additional properties, -// but only those specified in the schema can be set. -// elementId is the id in which to render the editor, -export function PreviewEditor (elementId) { - Editor.call(this, elementId) -} - -// Inherit Editor methods -PreviewEditor.prototype = Object.create(Editor.prototype) - -// After `PreviewEditor.prototype = Object.create(Editor.prototype)` instruction, -// PreviewEditor.prototype is a copy of Editor.prototype, -// so the `constructor` property of PreviewEditor.prototype is Editor, -// but a PreviewEditor object is created with the PreviewEditor constructor, -// so we override the `constructor` property of PreviewEditor.prototype to PreviewEditor -Object.defineProperty(PreviewEditor.prototype, 'constructor', { - value: PreviewEditor, - enumerable: false, // so that it does not appear in 'for in' loop - writable: true -}) - -PreviewEditor.prototype.updateSchema = function (schema) { - this.destroy() - this.jsonEditor = new JSONEditor(this.renderZone, { - schema, - no_additional_properties: true - }) -} - -// --------------- SchemaEditor subclass ------------------------------------- - -// create a JSON Editor for a JSON Schema, it adds a save button -// to save the Schema to a file, and also -// makes the 'Properties' buttons distinguishable to avoid confusion between -// schema properties and object properties. -// elementId is the id in which to render the editor, -export function SchemaEditor (elementId) { - Editor.call(this, elementId) - - // Check whether the node is a properties button for an object, - // and not for the schema of an object named properties - const isObjectPropertiesButton = function (node) { - // Does the path end in '.properties'? - if (node.matches('div[data-schemapath$=".properties"] > h3 > div > button.json-editor-btntype-properties')) { - const containingDiv = node.parentElement.parentElement.parentElement - const span = containingDiv.querySelector('h3 > span') - - // Is it an object properties or a property named properties? - if (span && span.innerText === 'properties') { - return true - } - } - return false - } - - this.observer = new MutationObserver(function (mutationsList, observer) { - for (const mutation of mutationsList) { - mutation.addedNodes.forEach(function (node) { - if (node.nodeType === 1) { - if (isObjectPropertiesButton(node)) { - node.querySelector('span').innerText = 'Add/Remove' - } else if (node.matches('button.json-editor-btntype-properties')) { - // For other properties buttons, remove the 'Properties' label, - // and use a cog as icon - const icon = node.querySelector('i') - icon.classList.remove('fa-pen') - icon.classList.add('fa-cog') - - const span = node.querySelector('span') - span.innerText = '' - } - } - }) - } - }) - - this.observer.observe(this.renderZone, { childList: true, subtree: true }) -} - -// Inherit Editor methods -SchemaEditor.prototype = Object.create(Editor.prototype) - -// After `SchemaEditor.prototype = Object.create(Editor.prototype)` instruction, -// SchemaEditor.prototype is a copy of Editor.prototype, -// so the `constructor` property of SchemaEditor.prototype is Editor, -// but a SchemaEditor object is created with the SchemaEditor constructor, -// so we override the `constructor` property of SchemaEditor.prototype to SchemaEditor -Object.defineProperty(SchemaEditor.prototype, 'constructor', { - value: SchemaEditor, - enumerable: false, // so that it does not appear in 'for in' loop - writable: true -}) - -// Override the updateSchema function -SchemaEditor.prototype.updateSchema = function (schema) { - this.destroy() - - // Add extra validation logic for integer Schemas that use the `range` format. - // For integer Schemas that use the `range` format we require that minimum and maximum properties are set, too. - const rangeIntegerValidator = function (schema, value, path) { - const errors = [] - if (value !== null && value.type === 'integer' && value.format === 'range') { - if (typeof value.minimum === 'undefined') { - errors.push({ - path, - property: 'format', - message: 'The range format requires that you specify both minimum and maximum properties, too.' - }) - } - } - return errors - } - - // Check that if minimum and maximum are specified, minimum <= maximum - const minMaxConsistenceValidator = function (schema, value, path) { - const errors = [] - if (value !== null && (value.type === 'integer' || value.type === 'number')) { - if (typeof value.minimum !== 'undefined' && value.minimum > value.maximum) { - errors.push({ - path, - property: 'maximum', - message: 'The maximum value must be greater than or equal than the minimum value.' - }) - } - } - return errors - } - - // Recreate the JSON-Editor - this.jsonEditor = new JSONEditor(this.renderZone, { - schema, - custom_validators: [rangeIntegerValidator, minMaxConsistenceValidator] - }) - - // Add a save button - const filename = 'schema.json' - const saveButtonLabel = 'Save' - - this.jsonEditor.on('ready', function () { - const button = this.root.getButton(saveButtonLabel, 'save', saveButtonLabel) - const buttonHolder = this.root.theme.getHeaderButtonHolder() - buttonHolder.appendChild(button) - this.root.header.parentNode.insertBefore(buttonHolder, this.root.header.nextSibling) - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const jsonEditor = this - button.addEventListener('click', function (e) { - e.preventDefault() - const contents = jsonEditor.getValue() - const blob = new Blob([JSON.stringify(contents, null, 2)], { - type: 'application/json;charset=utf-8' - }) - - if (window.navigator && window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(blob, filename) - } else { - const a = document.createElement('a') - a.download = filename - a.href = URL.createObjectURL(blob) - a.dataset.downloadurl = ['text/plain', a.download, a.href].join(':') - - a.dispatchEvent(new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: false - })) - } - }, false) - }) -} diff --git a/client/src/Components/jsonSchemaEditor/jsonEditor.jsx b/client/src/Components/jsonSchemaEditor/jsonEditor.jsx deleted file mode 100644 index 0e2c6049b1..0000000000 --- a/client/src/Components/jsonSchemaEditor/jsonEditor.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { Component } from 'react' -import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap' -import { SchemaEditor } from './json-schema-editor/editor' -import PropTypes from 'prop-types' -import * as JSONEditor from '@json-editor/json-editor' - -class JsonSchemaEditor extends Component { - constructor (props, content) { - super(props, content) - - this.schemaEditor = null - this.editorBox = React.createRef() - this.onChange = this.onChange.bind(this) - this.updateEditor = this.updateEditor.bind(this) - this.saved = '' - this.value = this.props.defaultValue - } - - onChange (newValue, e) { - const errors = this.schemaEditor.validate() - console.log(errors) - if (errors.length) { - alert('Invalid schema') - } else { - if (this.props.onChange) { - this.value = this.schemaEditor.jsonEditor.getValue() - this.props.onChange(this.schemaEditor.getJSON()) - } - } - } - - componentDidMount () { - this.schemaEditor = new SchemaEditor(this.editorBox) - this.updateEditor(this.props.schema, this.props.options) - } - - UNSAFE_componentWillReceiveProps ({ schema, options }) { - this.updateEditor(schema, options) - } - - updateEditor (schema, options) { - try { - const newJson = JSON.stringify(schema) - if (newJson !== this.save || options.counter !== this.props.options.counter) { - JSONEditor.JSONEditor.defaults.options.object_layout = options.layout - JSONEditor.JSONEditor.defaults.options = { - ...JSONEditor.JSONEditor.defaults.options, - ...options.booleanOptions - } - - this.save = newJson - this.schemaEditor.updateSchema(schema) - this.schemaEditor.jsonEditor.setValue(this.value) - this.schemaEditor.jsonEditor.on('change', this.onChange) - } - } catch (err) { - alert('Invalid json schema') - console.log(err) - } - } - - render () { - return ( - - - Schema Editor - - -
- - - - - ) - } -} - -JsonSchemaEditor.propTypes = { - schema: PropTypes.object.isRequired, - onChange: PropTypes.func -} - -export default JsonSchemaEditor diff --git a/client/src/Components/jsonSchemaEditor/metaSchema.js b/client/src/Components/jsonSchemaEditor/metaSchema.js deleted file mode 100644 index f984604778..0000000000 --- a/client/src/Components/jsonSchemaEditor/metaSchema.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, { Component } from 'react' -import ReactAce from 'react-ace-editor' -import { Card, CardHeader, CardBody, CardFooter, Button } from 'reactstrap' - -class MetaSchema extends Component { - constructor (props, content) { - super(props, content) - - this.editorBox = React.createRef() - this.onChange = this.onChange.bind(this) - this.setEditorValue = this.setEditorValue.bind(this) - this.onUpdateClick = this.onUpdateClick.bind(this) - this.saveJson = '' - this.state = {} - } - - onChange (newValue, e) { - // const editor = this.editorBox.current.editor; // The editor object is from Ace's API - // console.log(editor.getValue()); // Outputs the value of the editor - } - - componentDidMount () { - this.setEditorValue(JSON.stringify(this.props.value, null, 2)) - } - - componentWillReceiveProps ({ value }) { - this.setEditorValue(JSON.stringify(value, null, 2)) - } - - setEditorValue (json) { - if (this.editorBox.current && this.saveJson !== json) { - this.saveJson = json - this.editorBox.current.editor.setValue(this.saveJson) - this.editorBox.current.editor.session.getSelection().clearSelection() - this.editorBox.current.editor.resize() - } - } - - onUpdateClick () { - this.props.onSchemaSubmit(this.editorBox.current.editor.getValue()) - } - - render () { - return ( - - - Meta Schema - - - - - - - - - ) - } -} - -export default MetaSchema diff --git a/client/src/Components/jsonSchemaEditor/options.js b/client/src/Components/jsonSchemaEditor/options.js deleted file mode 100644 index c4abe945ce..0000000000 --- a/client/src/Components/jsonSchemaEditor/options.js +++ /dev/null @@ -1,82 +0,0 @@ -import { Component } from 'react' -import PropTypes from 'prop-types' - -import { - Card, - CardHeader, - CardBody, - CardFooter, - InputGroup, - InputGroupAddon, - Input -} from 'reactstrap' - -import { booleanOptions } from './defaultOptions' - -class EditorPreview extends Component { - constructor (props, content) { - super(props, content) - - this.onBooleanOptionsChange = this.onBooleanOptionsChange.bind(this) - this.onLayoutChange = this.onLayoutChange.bind(this) - } - - onBooleanOptionsChange (e) { - const options = e.target.options - const value = {} - for (let i = 0, l = options.length; i < l; i++) { - value[options[i].value] = options[i].selected - } - this.props.onChange({ - ...this.props, - booleanOptions: value, - counter: (this.props.options.counter || 0) + 1 - }) - } - - onLayoutChange (e) { - this.props.onChange({ - ...this.props.options, - layout: e.target.value, - counter: (this.props.options.counter || 0) + 1 - }) - } - - render () { - return ( - - - Boolean options - - - - {booleanOptions.map((option, key) => ( - - ))} - - - - - Object layout: - - - - - - - - ) - } -} - -EditorPreview.propTypes = { - onChange: PropTypes.func.isRequired, - options: PropTypes.object.isRequired -} - -export default EditorPreview diff --git a/client/src/Components/jsonSchemaEditor/styles.js b/client/src/Components/jsonSchemaEditor/styles.js deleted file mode 100644 index 39acbbb806..0000000000 --- a/client/src/Components/jsonSchemaEditor/styles.js +++ /dev/null @@ -1,10 +0,0 @@ -import { StyleSheet } from 'aphrodite' - -export default StyleSheet.create({ - main: { - padding: '15px 0' - }, - devSection: { - paddingTop: '15px' - } -}) diff --git a/client/src/Components/local/form-elements/message-creator/__snapshots__/index.spec.tsx.snap b/client/src/Components/local/form-elements/message-creator/__snapshots__/index.spec.tsx.snap index 2538f12f41..f4813f94e2 100644 --- a/client/src/Components/local/form-elements/message-creator/__snapshots__/index.spec.tsx.snap +++ b/client/src/Components/local/form-elements/message-creator/__snapshots__/index.spec.tsx.snap @@ -3,8 +3,322 @@ exports[`MessageCreator renders correctly 1`] = ` Array [
, + className="alert alert-danger fade" + role="alert" + style={ + Object { + "display": "none", + } + } + > +
+ Schema: +
+ + +
, +
+
+ UI Schema: +
+ + +
, +
+
+
+
+
+ Chat +
+
+
+
+
+
+ +
+
+
+ + +
+ +
+
+
+
+
+
+ +
+
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ Forces +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ ,
@@ -25,27 +339,5 @@ Array [ value="" />
, -
- - -
, ] `; diff --git a/client/src/Components/local/form-elements/message-creator/index.stories.tsx b/client/src/Components/local/form-elements/message-creator/index.stories.tsx index 86331f7767..eda4c362a3 100644 --- a/client/src/Components/local/form-elements/message-creator/index.stories.tsx +++ b/client/src/Components/local/form-elements/message-creator/index.stories.tsx @@ -56,7 +56,6 @@ const Template: StoryFn = (args) => { const postBack = (details: MessageDetails, message: any): void => { console.log('send message', details, message) } - return ( = ({ @@ -30,7 +30,6 @@ const MessageCreator: React.FC = ({ channel, gameDate, postBack, - customiseTemplate, messageOption, clearCachedCreatorMessage, draftMessage, @@ -38,7 +37,6 @@ const MessageCreator: React.FC = ({ modifyForSave }) => { const privateMessageRef = createRef() - const [formMessage, setFormMessage] = useState() const [clearForm, setClearForm] = useState(false) const [selectedSchema, setSelectedSchema] = useState(schema) const [privateValue, setPrivateValue] = useState('') @@ -46,8 +44,7 @@ const MessageCreator: React.FC = ({ const [messageContent, setMessageContent] = useState | undefined>(undefined) if (selectedForce === undefined) { throw new Error('selectedForce is undefined') } - const messageBeingEdited = useRef | string>('') - const sendMessage = (e: React.MouseEvent): void => { + const sendMessage = (val: { [property: string]: any }, e: React.MouseEvent) => { e.persist() const details: MessageDetails = { channel: channel.uniqid, @@ -62,7 +59,7 @@ const MessageCreator: React.FC = ({ timestamp: new Date().toISOString(), turnNumber: currentTurn } - + console.log('val', val) // special handling if this is a collab-channel if (channel.channelType === CHANNEL_COLLAB) { // populate the metadata @@ -84,16 +81,18 @@ const MessageCreator: React.FC = ({ privateMessageRef.current.value = '' } - if (formMessage.content === '') return + val = Object.fromEntries(Object.entries(val).filter(([_, value]) => value)) + if (isEmpty(val)) return // send the data setPrivateValue('') + setMessageContent(undefined) setClearForm(!clearForm) - postBack && postBack(details, formMessage, selectedSchema.title, CUSTOM_MESSAGE) + console.log('val', val) + postBack && postBack(details, val, messageOption, CUSTOM_MESSAGE) clearCachedCreatorMessage && clearCachedCreatorMessage([messageOption]) onMessageSend && onMessageSend(e) } - useEffect(() => { if (schema && (!selectedSchema || selectedSchema.title !== schema.title)) { setSelectedSchema(schema) @@ -124,11 +123,10 @@ const MessageCreator: React.FC = ({ setPrivateValue(e.target.value) } - const responseHandler = (val: { [property: string]: any }): void => { - setFormMessage(val) - messageBeingEdited.current = val + const storeNewValue = (documet: { [property: string]: any }) => { + setMessageContent(documet) } - + useEffect(() => { if (draftMessage) { const anyDraft = draftMessage as any @@ -140,6 +138,7 @@ const MessageCreator: React.FC = ({ } } }, [draftMessage]) + return ( <> = ({ details: selectedSchema, _id: channel.uniqid }} - customiseTemplate={customiseTemplate} + submitNewValue={sendMessage} + openCancelConfirmPopup={openConfirmPopup} messageId={messageOption} - formClassName={'form-group message-creator'} + formClassName={'form-group message-creator edt-disable'} title={messageOption} - storeNewValue={responseHandler} disabled={false} + viewSaveButton={true} gameDate={gameDate} clearForm={clearForm} messageContent={messageContent} + storeNewValue={storeNewValue} modifyForEdit={modifyForEdit} modifyForSave={modifyForSave} - /> - {privateMessage && ( -
-