diff --git a/src/actions/storage.action.ts b/src/actions/storage.action.ts index 0d73757c..613f2e8f 100644 --- a/src/actions/storage.action.ts +++ b/src/actions/storage.action.ts @@ -1725,6 +1725,7 @@ export const storageActionsThunk = { defaultBootloaderType?: IBootloaderType; qmkFirmwareVersion?: IBuildableFirmwareQmkFirmwareVersion; keyboardDirectoryName?: string; + supportCodeEditing?: boolean; } ): ThunkPromiseAction => async ( diff --git a/src/assets/images/documents/built-code.png b/src/assets/images/documents/built-code.png new file mode 100644 index 00000000..2ea7b480 Binary files /dev/null and b/src/assets/images/documents/built-code.png differ diff --git a/src/assets/images/documents/code-editing-for-building-firmware.png b/src/assets/images/documents/code-editing-for-building-firmware.png new file mode 100644 index 00000000..5ce12e77 Binary files /dev/null and b/src/assets/images/documents/code-editing-for-building-firmware.png differ diff --git a/src/assets/images/documents/turn-off-code-editing-feature.png b/src/assets/images/documents/turn-off-code-editing-feature.png new file mode 100644 index 00000000..9400bc43 Binary files /dev/null and b/src/assets/images/documents/turn-off-code-editing-feature.png differ diff --git a/src/components/catalog/keyboard/build/BuildParametersDialog.container.ts b/src/components/catalog/keyboard/build/BuildParametersDialog.container.ts index 076cea6a..0f1e0e38 100644 --- a/src/components/catalog/keyboard/build/BuildParametersDialog.container.ts +++ b/src/components/catalog/keyboard/build/BuildParametersDialog.container.ts @@ -14,6 +14,7 @@ const mapStateToProps = (state: RootState) => { buildableFirmwareKeymapFiles: state.entities.buildableFirmwareKeymapFiles, buildableFirmwareCodeParameterValues: state.catalog.keyboard.buildableFirmwareCodeParameterValues, + buildableFirmware: state.entities.buildableFirmware, }; }; export type BuildParametersDialogStateType = ReturnType; diff --git a/src/components/catalog/keyboard/build/BuildParametersDialog.tsx b/src/components/catalog/keyboard/build/BuildParametersDialog.tsx index b5f03fc2..2256fb85 100644 --- a/src/components/catalog/keyboard/build/BuildParametersDialog.tsx +++ b/src/components/catalog/keyboard/build/BuildParametersDialog.tsx @@ -7,6 +7,10 @@ import { DialogActions, DialogContent, DialogTitle, + Divider, + FormControl, + FormControlLabel, + FormLabel, Grid, List, ListItem, @@ -16,11 +20,14 @@ import { ListSubheader, MenuItem, Paper, + Radio, + RadioGroup, Select, TextField, Typography, } from '@mui/material'; import { + IBuildableFirmware, IBuildableFirmwareFile, IBuildableFirmwareFileType, } from '../../../../services/storage/Storage'; @@ -34,6 +41,7 @@ import { import { IBuildableFirmwareCodeParameter, IBuildableFirmwareCodeParameterValues, + IBuildableFirmwareCodeValueType, } from '../../../../store/state'; type OwnProps = { @@ -92,7 +100,39 @@ export default function BuildParametersDialog( ? newParameterValues.keyboard : newParameterValues.keymap; const newParameterValueMap = newValueMap[targetFirmwareFile.file.id]; - newParameterValueMap[targetParameter.name].value = newValue; + newParameterValueMap.parameters[targetParameter.name].value = newValue; + props.updateBuildableFirmwareCodeParameterValues!(newParameterValues); + }; + + const onChangeValueType = ( + targetFirmwareFile: SelectedFirmwareFile, + newType: IBuildableFirmwareCodeValueType + ) => { + const newParameterValues = structuredClone( + props.buildableFirmwareCodeParameterValues + ) as IBuildableFirmwareCodeParameterValues; + const newValueMap = + targetFirmwareFile.type === 'keyboard' + ? newParameterValues.keyboard + : newParameterValues.keymap; + const newParameterValueMap = newValueMap[targetFirmwareFile.file.id]; + newParameterValueMap.type = newType; + props.updateBuildableFirmwareCodeParameterValues!(newParameterValues); + }; + + const onChangeCode = ( + targetFirmwareFile: SelectedFirmwareFile, + newCode: string + ) => { + const newParameterValues = structuredClone( + props.buildableFirmwareCodeParameterValues + ) as IBuildableFirmwareCodeParameterValues; + const newValueMap = + targetFirmwareFile.type === 'keyboard' + ? newParameterValues.keyboard + : newParameterValues.keymap; + const newParameterValueMap = newValueMap[targetFirmwareFile.file.id]; + newParameterValueMap.code = newCode; props.updateBuildableFirmwareCodeParameterValues!(newParameterValues); }; @@ -117,23 +157,28 @@ export default function BuildParametersDialog( ? props.buildableFirmwareCodeParameterValues!.keyboard : props.buildableFirmwareCodeParameterValues!.keymap; const newParameterValueMap = newValueMap[file.id]; - const originalParameterValueMapEntries = Object.entries( - originalParameterValueMap - ); - const newParameterValueMapEntries = Object.entries( - newParameterValueMap - ).filter( - ([parameterName, parameter]) => - parameter.value === originalParameterValueMap[parameterName].value - ); - return ( - originalParameterValueMapEntries.length !== - newParameterValueMapEntries.length - ); + if (newParameterValueMap.type === 'code') { + return originalParameterValueMap.code !== newParameterValueMap.code; + } else { + const originalParameterValueMapEntries = Object.entries( + originalParameterValueMap.parameters + ); + const newParameterValueMapEntries = Object.entries( + newParameterValueMap.parameters + ).filter( + ([parameterName, parameter]) => + parameter.value === + originalParameterValueMap.parameters[parameterName].value + ); + return ( + originalParameterValueMapEntries.length !== + newParameterValueMapEntries.length + ); + } }; return ( - + Build Parameters - @@ -283,6 +331,128 @@ function FirmwareFileListItem(props: FirmwareFileListItemProps) { ); } +type EditorContainerProps = { + selectedFirmwareFile: SelectedFirmwareFile; + buildableFirmwareCodeParameterValues: IBuildableFirmwareCodeParameterValues; + buildableFirmware: IBuildableFirmware; + onChangeParameterValue: ( + // eslint-disable-next-line no-unused-vars + selectedFirmwareFile: SelectedFirmwareFile, + // eslint-disable-next-line no-unused-vars + selectedParameter: IBuildableFirmwareCodeParameter, + // eslint-disable-next-line no-unused-vars + newValue: string + ) => void; + onChangeValueType: ( + // eslint-disable-next-line no-unused-vars + selectedFirmwareFile: SelectedFirmwareFile, + // eslint-disable-next-line no-unused-vars + newType: IBuildableFirmwareCodeValueType + ) => void; + onChangeCode: ( + // eslint-disable-next-line no-unused-vars + selectedFirmwareFile: SelectedFirmwareFile, + // eslint-disable-next-line no-unused-vars + newCode: string + ) => void; +}; + +function EditorContainer(props: EditorContainerProps) { + const valueMap = + props.selectedFirmwareFile.type === 'keyboard' + ? props.buildableFirmwareCodeParameterValues.keyboard + : props.buildableFirmwareCodeParameterValues.keymap; + const parameterValueMap = valueMap[props.selectedFirmwareFile.file.id]; + + return ( + + {props.buildableFirmware.supportCodeEditing && ( + + + + How do you want to customize this file? + + { + props.onChangeValueType( + props.selectedFirmwareFile, + event.target.value as IBuildableFirmwareCodeValueType + ); + }} + > + } + label="By selecting and filling in each parameter" + /> + } + label="By editing a code" + /> + + + + + )} + {parameterValueMap.type === 'code' ? ( + + ) : ( + + )} + + ); +} + +type CodeEditorProps = { + selectedFirmwareFile: SelectedFirmwareFile; + buildableFirmwareCodeParameterValues: IBuildableFirmwareCodeParameterValues; + onChangeCode: ( + // eslint-disable-next-line no-unused-vars + selectedFirmwareFile: SelectedFirmwareFile, + // eslint-disable-next-line no-unused-vars + newCode: string + ) => void; +}; + +function CodeEditor(props: CodeEditorProps) { + const valueMap = + props.selectedFirmwareFile.type === 'keyboard' + ? props.buildableFirmwareCodeParameterValues.keyboard + : props.buildableFirmwareCodeParameterValues.keymap; + const parameterValueMap = valueMap[props.selectedFirmwareFile.file.id]; + + return ( + { + props.onChangeCode(props.selectedFirmwareFile, event.target.value); + }} + /> + ); +} + type ParameterEditorProps = { selectedFirmwareFile: SelectedFirmwareFile; buildableFirmwareCodeParameterValues: IBuildableFirmwareCodeParameterValues; @@ -303,7 +473,7 @@ function ParameterEditors(props: ParameterEditorProps) { : props.buildableFirmwareCodeParameterValues.keymap; const parameterValueMap = valueMap[props.selectedFirmwareFile.file.id]; - const parameterValueMapEntries = Object.entries(parameterValueMap); + const parameterValueMapEntries = Object.entries(parameterValueMap.parameters); return ( {parameterValueMapEntries.length === 0 ? ( diff --git a/src/components/catalog/keyboard/build/CatalogBuild.tsx b/src/components/catalog/keyboard/build/CatalogBuild.tsx index e0ea8bda..835a8c07 100644 --- a/src/components/catalog/keyboard/build/CatalogBuild.tsx +++ b/src/components/catalog/keyboard/build/CatalogBuild.tsx @@ -4,33 +4,9 @@ import { CatalogBuildActionsType, CatalogBuildStateType, } from './CatalogBuild.container'; -import { - Box, - Button, - Card, - CardActions, - CardContent, - FormControlLabel, - Paper, - Step, - StepLabel, - Stepper, - Switch, - Tab, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Tabs, - TextField, - Typography, -} from '@mui/material'; -import moment from 'moment'; +import { Box, Button, FormControlLabel, Paper, Switch } from '@mui/material'; import { IBuildableFirmwareFile, - IBuildableFirmwareFileType, IFirmwareBuildingTask, } from '../../../../services/storage/Storage'; import { sendEventToGoogleAnalytics } from '../../../../utils/GoogleAnalytics'; @@ -40,8 +16,12 @@ import { IBuildableFirmwareCodeParameterValueMap, IBuildableFirmwareCodeParameterValues, } from '../../../../store/state'; -import { extractBuildableFirmwareCodeParameters } from '../../../../services/build/FirmwareCodeParser'; +import { + extractBuildableFirmwareCodeParameters, + replaceBuildableFirmwareCodeWithParameterDefaultValues, +} from '../../../../services/build/FirmwareCodeParser'; import ConfirmDialog from '../../../common/confirm/ConfirmDialog'; +import { FirmwareBuildingTaskCard } from './FirmwareBuildingTaskCard'; type OwnProps = {}; type CatalogBuildProps = OwnProps & @@ -96,7 +76,17 @@ export default function CatalogBuild(props: CatalogBuildProps) { return files.reduce( (valueMap, file) => { const parameters = extractBuildableFirmwareCodeParameters(file.content); - valueMap[file.id] = parameters.reduce<{ + if (!valueMap[file.id]) { + valueMap[file.id] = { + type: 'parameters', + parameters: {}, + code: replaceBuildableFirmwareCodeWithParameterDefaultValues( + file.content, + parameters + ), + }; + } + valueMap[file.id].parameters = parameters.reduce<{ [parameterName: string]: IBuildableFirmwareCodeParameterValue; }>((values, parameter) => { values[parameter.name] = { @@ -185,17 +175,35 @@ export default function CatalogBuild(props: CatalogBuildProps) { const createBuildableFirmwareCodeParameterNameValueMap = ( valueMap: IBuildableFirmwareCodeParameterValueMap - ): { [fileId: string]: { [parameterName: string]: string } } => { + ): { + [fileId: string]: { + type: string; + parameters?: { [parameterName: string]: string }; + code?: string; + }; + } => { return Object.entries(valueMap).reduce<{ - [fileId: string]: { [parameterName: string]: string }; + [fileId: string]: { + type: string; + parameters?: { [parameterName: string]: string }; + code?: string; + }; }>((result, [fileId, valueMap]) => { - result[fileId] = Object.entries(valueMap).reduce<{ - [parameterName: string]: string; - }>((result, [parameterName, parameterValue]) => { - result[parameterName] = parameterValue.value; + if (valueMap.type === 'code') { + result[fileId] = { type: 'code', code: valueMap.code }; + return result; + } else { + result[fileId] = { + type: 'parameters', + parameters: Object.entries(valueMap.parameters).reduce<{ + [parameterName: string]: string; + }>((result, [parameterName, parameterValue]) => { + result[parameterName] = parameterValue.value; + return result; + }, {}), + }; return result; - }, {}); - return result; + } }, {}); }; @@ -207,9 +215,23 @@ export default function CatalogBuild(props: CatalogBuildProps) { }); setOpenBuildParametersDialog(false); const parameterValues: { - keyboard: { [fileId: string]: { [parameterName: string]: string } }; - keymap: { [fileId: string]: { [parameterName: string]: string } }; + version: number; + keyboard: { + [fileId: string]: { + type: string; + parameters?: { [parameterName: string]: string }; + code?: string; + }; + }; + keymap: { + [fileId: string]: { + type: string; + parameters?: { [parameterName: string]: string }; + code?: string; + }; + }; } = { + version: 2, keyboard: createBuildableFirmwareCodeParameterNameValueMap( props.buildableFirmwareCodeParameterValues!.keyboard ), @@ -310,261 +332,3 @@ export default function CatalogBuild(props: CatalogBuildProps) { ); } - -type FirmwareBuildingTaskCardProps = { - task: IFirmwareBuildingTask; - buildableFirmwareKeyboardFiles: IBuildableFirmwareFile[]; - buildableFirmwareKeymapFiles: IBuildableFirmwareFile[]; - // eslint-disable-next-line no-unused-vars - onClickDownload: (task: IFirmwareBuildingTask) => void; - // eslint-disable-next-line no-unused-vars - onClickFlash: (task: IFirmwareBuildingTask) => void; - // eslint-disable-next-line no-unused-vars - onClickDelete: (task: IFirmwareBuildingTask) => void; - onChangeDescription: ( - // eslint-disable-next-line no-unused-vars - task: IFirmwareBuildingTask, - // eslint-disable-next-line no-unused-vars - description: string - ) => void; -}; - -function FirmwareBuildingTaskCard(props: FirmwareBuildingTaskCardProps) { - const [description, setDescription] = useState( - props.task.description - ); - const [logTabIndex, setLogTabIndex] = useState(0); - - const isTaskCompleted = (task: IFirmwareBuildingTask): boolean => { - return task.status === 'success' || task.status === 'failure'; - }; - - const createActiveStepNumber = (task: IFirmwareBuildingTask): number => { - return task.status === 'waiting' ? 0 : task.status === 'building' ? 1 : 2; - }; - - const onLogTabChange = (event: React.SyntheticEvent, newValue: number) => { - setLogTabIndex(newValue); - }; - - const createBuildableFirmwareFileParameterNameValueMap = ( - parametersJson: string - ): { - keyboard: { [fileId: string]: { [parameterName: string]: string } }; - keymap: { [fileId: string]: { [parameterName: string]: string } }; - } => { - return JSON.parse(parametersJson) as { - keyboard: { [fileId: string]: { [parameterName: string]: string } }; - keymap: { [fileId: string]: { [parameterName: string]: string } }; - }; - }; - - const createBuildableFirmwareFileParameterValues = ( - parametersJson: string - ): { - type: IBuildableFirmwareFileType; - file: IBuildableFirmwareFile; - parameterName: string; - parameterValue: string; - }[] => { - const nameValueMap = - createBuildableFirmwareFileParameterNameValueMap(parametersJson); - const result: { - type: IBuildableFirmwareFileType; - file: IBuildableFirmwareFile; - parameterName: string; - parameterValue: string; - }[] = []; - Object.entries(nameValueMap.keyboard).forEach(([fileId, nameValueMap]) => { - Object.entries(nameValueMap).forEach( - ([parameterName, parameterValue]) => { - result.push({ - type: 'keyboard', - file: props.buildableFirmwareKeyboardFiles.find( - (file) => file.id === fileId - )!, - parameterName, - parameterValue, - }); - } - ); - }); - Object.entries(nameValueMap.keymap).forEach(([fileId, nameValueMap]) => { - Object.entries(nameValueMap).forEach( - ([parameterName, parameterValue]) => { - result.push({ - type: 'keymap', - file: props.buildableFirmwareKeymapFiles.find( - (file) => file.id === fileId - )!, - parameterName, - parameterValue, - }); - } - ); - }); - return result; - }; - - return ( - - - - Task ID: {props.task.id} - - Created at:{' '} - {moment(props.task.createdAt).format('YYYY-MM-DD hh:mm:ss')} - - - Updated at:{' '} - {moment(props.task.updatedAt).format('YYYY-MM-DD hh:mm:ss')} - - - - - Waiting - - - Building - - {props.task.status === 'success' ? ( - - Success - - ) : props.task.status === 'failure' ? ( - - Failure - - ) : ( - - Completed - - )} - - - { - setDescription(event.target.value); - }} - onBlur={() => { - props.onChangeDescription(props.task, description); - }} - /> - - - - - {isTaskCompleted(props.task) && [ - , - , - ]} - - {logTabIndex === 0 ? ( - - - - - File - Parameter - Value - - - - {createBuildableFirmwareFileParameterValues( - props.task.parametersJson - ).map((parameterValue, index) => ( - - {`${parameterValue.type}: ${parameterValue.file.path}`} - {parameterValue.parameterName} - {parameterValue.parameterValue} - - ))} - -
-
- ) : logTabIndex === 1 ? ( - - ) : ( - - )} -
-
- - {props.task.status === 'success' && ( - - )} - {props.task.status === 'success' && ( - - )} - {isTaskCompleted(props.task) && ( - - )} - -
- ); -} diff --git a/src/components/catalog/keyboard/build/FirmwareBuildingTaskCard.tsx b/src/components/catalog/keyboard/build/FirmwareBuildingTaskCard.tsx new file mode 100644 index 00000000..9cf24bf2 --- /dev/null +++ b/src/components/catalog/keyboard/build/FirmwareBuildingTaskCard.tsx @@ -0,0 +1,461 @@ +import { + IBuildableFirmwareFile, + IBuildableFirmwareFileType, + IFirmwareBuildingTask, +} from '../../../../services/storage/Storage'; +import React, { useState } from 'react'; +import { + Box, + Button, + Card, + CardActions, + CardContent, + List, + ListItemButton, + ListItemText, + Paper, + Step, + StepLabel, + Stepper, + Tab, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Tabs, + TextField, + Typography, +} from '@mui/material'; +import moment from 'moment/moment'; + +type ParameterNameValueMap = { + [parameterName: string]: string; +}; + +type ParametersJsonVersion1 = { + keyboard: { [fileId: string]: ParameterNameValueMap }; + keymap: { [fileId: string]: ParameterNameValueMap }; +}; + +type ParametersJsonVersion2 = { + version: number; + keyboard: { + [fileId: string]: { + type: string; + parameters?: ParameterNameValueMap; + code?: string; + }; + }; + keymap: { + [fileId: string]: { + type: string; + parameters?: ParameterNameValueMap; + code?: string; + }; + }; +}; + +type FirmwareBuildingTaskCardProps = { + task: IFirmwareBuildingTask; + buildableFirmwareKeyboardFiles: IBuildableFirmwareFile[]; + buildableFirmwareKeymapFiles: IBuildableFirmwareFile[]; + // eslint-disable-next-line no-unused-vars + onClickDownload: (task: IFirmwareBuildingTask) => void; + // eslint-disable-next-line no-unused-vars + onClickFlash: (task: IFirmwareBuildingTask) => void; + // eslint-disable-next-line no-unused-vars + onClickDelete: (task: IFirmwareBuildingTask) => void; + onChangeDescription: ( + // eslint-disable-next-line no-unused-vars + task: IFirmwareBuildingTask, + // eslint-disable-next-line no-unused-vars + description: string + ) => void; +}; + +export function FirmwareBuildingTaskCard(props: FirmwareBuildingTaskCardProps) { + const [description, setDescription] = useState( + props.task.description + ); + const [logTabIndex, setLogTabIndex] = useState(0); + const [selectedBuildCodeIndex, setSelectedBuildCodeIndex] = useState< + number | undefined + >(undefined); + + const isTaskCompleted = (task: IFirmwareBuildingTask): boolean => { + return task.status === 'success' || task.status === 'failure'; + }; + + const createActiveStepNumber = (task: IFirmwareBuildingTask): number => { + return task.status === 'waiting' ? 0 : task.status === 'building' ? 1 : 2; + }; + + const onLogTabChange = (event: React.SyntheticEvent, newValue: number) => { + setLogTabIndex(newValue); + }; + + const createBuildableFirmwareFileParameterNameValueMap = ( + parametersJson: string + ): { + keyboard: { [fileId: string]: ParameterNameValueMap }; + keymap: { [fileId: string]: ParameterNameValueMap }; + } => { + const json = JSON.parse(parametersJson); + if (json.version && json.version === 2) { + const values = json as ParametersJsonVersion2; + return { + keyboard: Object.entries(values.keyboard).reduce<{ + [fileId: string]: ParameterNameValueMap; + }>((result, [fileId, value]) => { + if (value.type === 'parameters') { + result[fileId] = value.parameters!; + } + return result; + }, {}), + keymap: Object.entries(values.keymap).reduce<{ + [fileId: string]: ParameterNameValueMap; + }>((result, [fileId, value]) => { + if (value.type === 'parameters') { + result[fileId] = value.parameters!; + } + return result; + }, {}), + }; + } else { + return json as ParametersJsonVersion1; + } + }; + + const createBuildableFirmwareFileParameterValues = ( + parametersJson: string + ): { + type: IBuildableFirmwareFileType; + file: IBuildableFirmwareFile; + parameterName: string; + parameterValue: string; + }[] => { + const nameValueMap = + createBuildableFirmwareFileParameterNameValueMap(parametersJson); + const result: { + type: IBuildableFirmwareFileType; + file: IBuildableFirmwareFile; + parameterName: string; + parameterValue: string; + }[] = []; + Object.entries(nameValueMap.keyboard).forEach(([fileId, nameValueMap]) => { + Object.entries(nameValueMap).forEach( + ([parameterName, parameterValue]) => { + result.push({ + type: 'keyboard', + file: props.buildableFirmwareKeyboardFiles.find( + (file) => file.id === fileId + )!, + parameterName, + parameterValue, + }); + } + ); + }); + Object.entries(nameValueMap.keymap).forEach(([fileId, nameValueMap]) => { + Object.entries(nameValueMap).forEach( + ([parameterName, parameterValue]) => { + result.push({ + type: 'keymap', + file: props.buildableFirmwareKeymapFiles.find( + (file) => file.id === fileId + )!, + parameterName, + parameterValue, + }); + } + ); + }); + return result; + }; + + const createBuildableFirmwareFileNameCodeMap = ( + parametersJson: string + ): { + keyboard: { [fileId: string]: string }; + keymap: { [fileId: string]: string }; + } => { + const json = JSON.parse(parametersJson); + if (json.version && json.version === 2) { + const values = json as ParametersJsonVersion2; + return { + keyboard: Object.entries(values.keyboard).reduce<{ + [fileId: string]: string; + }>((result, [fileId, value]) => { + if (value.type === 'code') { + result[fileId] = value.code!; + } + return result; + }, {}), + keymap: Object.entries(values.keymap).reduce<{ + [fileId: string]: string; + }>((result, [fileId, value]) => { + if (value.type === 'code') { + result[fileId] = value.code!; + } + return result; + }, {}), + }; + } else { + // The version 1 does not have code. + return { + keyboard: {}, + keymap: {}, + }; + } + }; + + const createBuildableFirmwareFileCodes = ( + parametersJson: string + ): { + type: IBuildableFirmwareFileType; + file: IBuildableFirmwareFile; + code: string; + }[] => { + const nameCodeMap = createBuildableFirmwareFileNameCodeMap(parametersJson); + const result: { + type: IBuildableFirmwareFileType; + file: IBuildableFirmwareFile; + code: string; + }[] = []; + Object.entries(nameCodeMap.keyboard).forEach(([fileId, code]) => { + result.push({ + type: 'keyboard', + file: props.buildableFirmwareKeyboardFiles.find( + (file) => file.id === fileId + )!, + code, + }); + }); + Object.entries(nameCodeMap.keymap).forEach(([fileId, code]) => { + result.push({ + type: 'keymap', + file: props.buildableFirmwareKeymapFiles.find( + (file) => file.id === fileId + )!, + code, + }); + }); + return result; + }; + + const buildCodeArray = createBuildableFirmwareFileCodes( + props.task.parametersJson + ); + + return ( + + + + Task ID: {props.task.id} + + Created at:{' '} + {moment(props.task.createdAt).format('YYYY-MM-DD hh:mm:ss')} + + + Updated at:{' '} + {moment(props.task.updatedAt).format('YYYY-MM-DD hh:mm:ss')} + + + + + Waiting + + + Building + + {props.task.status === 'success' ? ( + + Success + + ) : props.task.status === 'failure' ? ( + + Failure + + ) : ( + + Completed + + )} + + + { + setDescription(event.target.value); + }} + onBlur={() => { + props.onChangeDescription(props.task, description); + }} + /> + + + + + + {isTaskCompleted(props.task) && [ + , + , + ]} + + {logTabIndex === 0 ? ( + + + + + File + Parameter + Value + + + + {createBuildableFirmwareFileParameterValues( + props.task.parametersJson + ).map((parameterValue, index) => ( + + {`${parameterValue.type}: ${parameterValue.file.path}`} + {parameterValue.parameterName} + {parameterValue.parameterValue} + + ))} + +
+
+ ) : logTabIndex === 1 ? ( + + + + {buildCodeArray.map((entry, index) => ( + { + setSelectedBuildCodeIndex(index); + }} + selected={selectedBuildCodeIndex === index} + > + + + ))} + + + + + ) : logTabIndex === 2 ? ( + + ) : ( + + )} +
+
+ + {props.task.status === 'success' && ( + + )} + {props.task.status === 'success' && ( + + )} + {isTaskCompleted(props.task) && ( + + )} + +
+ ); +} diff --git a/src/components/documents/Documents.tsx b/src/components/documents/Documents.tsx index 068c0943..7178e9c4 100644 --- a/src/components/documents/Documents.tsx +++ b/src/components/documents/Documents.tsx @@ -16,6 +16,7 @@ import { useParams } from 'react-router-dom'; import SupportQmk022 from './support-qmk-022/SupportQmk022'; import Encoders from './encoders/Encoders'; import Build from './build/Build'; +import SupportCodeEditing from './support-code-editing/SupportCodeEditing'; type RouteParams = { docId: string; @@ -57,7 +58,10 @@ export default function Documents(props: DocumentsPropsType) { sendEventToGoogleAnalytics('docs/encoders'); } else if (docId === 'build') { page = ; - sendEventToGoogleAnalytics('docs/encoders'); + sendEventToGoogleAnalytics('docs/build'); + } else if (docId === 'support-code-editing') { + page = ; + sendEventToGoogleAnalytics('docs/support-code-editing'); } else { page = ; } diff --git a/src/components/documents/index/Index.tsx b/src/components/documents/index/Index.tsx index a31e8df9..7ee89950 100644 --- a/src/components/documents/index/Index.tsx +++ b/src/components/documents/index/Index.tsx @@ -7,6 +7,11 @@ export default function Index() { News
+ + + [Nov 30th, 2023] Remap supports code editing for building firmware + + [Nov 9th, 2023] Remap supports building firmware diff --git a/src/components/documents/support-code-editing/SupportCodeEditing.tsx b/src/components/documents/support-code-editing/SupportCodeEditing.tsx new file mode 100644 index 00000000..591f647c --- /dev/null +++ b/src/components/documents/support-code-editing/SupportCodeEditing.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { Alert, Typography } from '@mui/material'; +import CodeEditingForBuildingFirmwareImage from '../../../assets/images/documents/code-editing-for-building-firmware.png'; +import BuiltCodeImage from '../../../assets/images/documents/built-code.png'; +import TurnOffCodeEditingFeatureImage from '../../../assets/images/documents/turn-off-code-editing-feature.png'; + +export default function SupportCodeEditing() { + return ( + + + Remap supports a code editing for building a firmware + +
+ + [Nov 30th, 2023] This feature is currently in beta. It is possible + that the feature is changed or removed without notice. + +
+
+ + Remap supports a code editing for building a firmware. Users can edit + files of registered keyboard firmware directly before building it. + + Code Editing for Building Firmware + + All features provided by QMK Firmware are available by using the + Firmware Code Editing feature. Remap cannot still provide many + features: using Combos, Key Overrides, customizing Tap Dance and so + on. But, users can get them by editing a code. + + Built Code + + If a keyboard designer does not want to edit codes by users, the code + editing feature can be turned off. + + Turn off code editing feature + + This feature brings users to customize a firmware more easily and + deeply without preparing a development environment locally. In other + words, Remap becomes an IDE to build a firmware. + + + We expect that many users can customize their keyboard more easily and + deeply with this feature. + +
+
+ + Written on: November 30th, 2023 + +
+
+ ); +} diff --git a/src/components/keyboards/editdefinition/buildform/BuildForm.container.ts b/src/components/keyboards/editdefinition/buildform/BuildForm.container.ts index 51065a2d..5a165d59 100644 --- a/src/components/keyboards/editdefinition/buildform/BuildForm.container.ts +++ b/src/components/keyboards/editdefinition/buildform/BuildForm.container.ts @@ -62,6 +62,16 @@ const mapDispatchToProps = (dispatch: any) => { }) ); }, + updateBuildableFirmwareSupportCodeEditing: ( + keyboardDefinitionId: string, + supportCodeEditing: boolean + ) => { + dispatch( + storageActionsThunk.updateBuildableFirmware(keyboardDefinitionId, { + supportCodeEditing, + }) + ); + }, updateBuildableFirmwareKeyboardDirectoryName: ( keyboardDefinitionId: string, keyboardDirectoryName: string diff --git a/src/components/keyboards/editdefinition/buildform/BuildForm.tsx b/src/components/keyboards/editdefinition/buildform/BuildForm.tsx index 70540dd8..e569ba11 100644 --- a/src/components/keyboards/editdefinition/buildform/BuildForm.tsx +++ b/src/components/keyboards/editdefinition/buildform/BuildForm.tsx @@ -178,6 +178,13 @@ export default function BuildForm(props: BuildFormProps) { ); }; + const onChangeSupportCodeEditing = (event: SelectChangeEvent) => { + props.updateBuildableFirmwareSupportCodeEditing!( + props.keyboardDefinition!.id, + event.target.value === 'true' + ); + }; + const onChangeKeyboardDirectoryName = () => { if ( keyboardDirectoryName !== props.buildableFirmware!.keyboardDirectoryName @@ -202,7 +209,7 @@ export default function BuildForm(props: BuildFormProps) { + + + Support Code Editing + + + + + Info: + [Nov 30th 2023] Remap supports a code editing for building a firmware. + See{' '} + + more detail + + . + Info: [Nov 11th 2023] The firmware building feature has been released. See{' '} diff --git a/src/services/build/FirmwareCodeParser.test.ts b/src/services/build/FirmwareCodeParser.test.ts index b2b899e8..0630653f 100644 --- a/src/services/build/FirmwareCodeParser.test.ts +++ b/src/services/build/FirmwareCodeParser.test.ts @@ -1,6 +1,54 @@ -import { extractBuildableFirmwareCodeParameters } from './FirmwareCodeParser'; +import { + extractBuildableFirmwareCodeParameters, + replaceBuildableFirmwareCodeWithParameterDefaultValues, +} from './FirmwareCodeParser'; +import { IBuildableFirmwareCodeParameter } from '../../store/state'; describe('FirmwareCodeParser', () => { + describe('replaceBuildableFirmwareCodeWithParameterDefaultValues', () => { + test('should replace with default values', () => { + const input = ` + + + + `; + const parameters: IBuildableFirmwareCodeParameter[] = + extractBuildableFirmwareCodeParameters(input); + + const replaced = replaceBuildableFirmwareCodeWithParameterDefaultValues( + input, + parameters + ); + + expect(replaced).toEqual(` + baz + john + 20 + `); + }); + + test('should replace with default values without the parameter which has no options', () => { + const input = ` + + + + `; + const parameters: IBuildableFirmwareCodeParameter[] = + extractBuildableFirmwareCodeParameters(input); + + const replaced = replaceBuildableFirmwareCodeWithParameterDefaultValues( + input, + parameters + ); + + expect(replaced).toEqual(` + + john + 20 + `); + }); + }); + describe('extractBuildableFirmwareCodeParameters', () => { test('should extract parameters from source', () => { const source = ` diff --git a/src/services/build/FirmwareCodeParser.ts b/src/services/build/FirmwareCodeParser.ts index 5d884afa..4ae9ae7f 100644 --- a/src/services/build/FirmwareCodeParser.ts +++ b/src/services/build/FirmwareCodeParser.ts @@ -18,6 +18,22 @@ const isValidType = ( return ['select', 'text', 'number'].includes(type); }; +export const replaceBuildableFirmwareCodeWithParameterDefaultValues = ( + input: string, + parameters: IBuildableFirmwareCodeParameter[] +): string => { + const sortedParameters = parameters.sort( + (a, b) => b.startPosition - a.startPosition + ); + return sortedParameters.reduce((result, parameter): string => { + return ( + result.substring(0, parameter.startPosition) + + parameter.default + + result.substring(parameter.endPosition) + ); + }, input); +}; + export const extractBuildableFirmwareCodeParameters = ( input: string ): IBuildableFirmwareCodeParameter[] => { diff --git a/src/services/provider/Firebase.ts b/src/services/provider/Firebase.ts index cb99d1bd..6add49ec 100644 --- a/src/services/provider/Firebase.ts +++ b/src/services/provider/Firebase.ts @@ -1584,6 +1584,10 @@ export class FirebaseProvider implements IStorage, IAuth { defaultBootloaderType: doc.data()!.defaultBootloaderType, qmkFirmwareVersion: doc.data()!.qmkFirmwareVersion, keyboardDirectoryName: doc.data()!.keyboardDirectoryName || '', + supportCodeEditing: + doc.data()!.supportCodeEditing !== undefined + ? doc.data()!.supportCodeEditing + : true, createdAt: doc.data()!.createdAt.toDate(), updatedAt: doc.data()!.updatedAt.toDate(), }); @@ -1600,6 +1604,7 @@ export class FirebaseProvider implements IStorage, IAuth { BUILDABLE_FIRMWARE_QMK_FIRMWARE_VERSION.length - 1 ], keyboardDirectoryName: '', + supportCodeEditing: true, createdAt: now, updatedAt: now, }; @@ -1637,6 +1642,10 @@ export class FirebaseProvider implements IStorage, IAuth { defaultBootloaderType: doc.data()!.defaultBootloaderType, qmkFirmwareVersion: doc.data()!.qmkFirmwareVersion, keyboardDirectoryName: doc.data()!.keyboardDirectoryName || '', + supportCodeEditing: + doc.data()!.supportCodeEditing !== undefined + ? doc.data()!.supportCodeEditing + : true, createdAt: doc.data()!.createdAt.toDate(), updatedAt: doc.data()!.updatedAt.toDate(), }); @@ -1669,6 +1678,10 @@ export class FirebaseProvider implements IStorage, IAuth { defaultBootloaderType: doc.data()!.defaultBootloaderType, qmkFirmwareVersion: doc.data()!.qmkFirmwareVersion, keyboardDirectoryName: doc.data()!.keyboardDirectoryName || '', + supportCodeEditing: + doc.data()!.supportCodeEditing !== undefined + ? doc.data()!.supportCodeEditing + : true, createdAt: doc.data()!.createdAt.toDate(), updatedAt: doc.data()!.updatedAt.toDate(), }; @@ -1779,6 +1792,7 @@ export class FirebaseProvider implements IStorage, IAuth { defaultBootloaderType?: IBootloaderType; qmkFirmwareVersion?: IBuildableFirmwareQmkFirmwareVersion; keyboardDirectoryName?: string; + supportCodeEditing?: boolean; } ): Promise> { try { @@ -1803,6 +1817,9 @@ export class FirebaseProvider implements IStorage, IAuth { if (options.keyboardDirectoryName !== undefined) { buildableFirmware.keyboardDirectoryName = options.keyboardDirectoryName; } + if (options.supportCodeEditing !== undefined) { + buildableFirmware.supportCodeEditing = options.supportCodeEditing; + } buildableFirmware.updatedAt = new Date(); await this.db .collection('build') diff --git a/src/services/storage/Storage.ts b/src/services/storage/Storage.ts index 849d40fb..977ae2c8 100644 --- a/src/services/storage/Storage.ts +++ b/src/services/storage/Storage.ts @@ -283,6 +283,7 @@ export type IBuildableFirmware = { defaultBootloaderType: IBootloaderType; qmkFirmwareVersion: IBuildableFirmwareQmkFirmwareVersion; keyboardDirectoryName: string; + supportCodeEditing: boolean; createdAt: Date; updatedAt: Date; }; @@ -489,6 +490,8 @@ export interface IStorage { enabled?: boolean; defaultBootloaderType?: IBootloaderType; keyboardDirectoryName?: string; + qmkFirmwareVersion?: IBuildableFirmwareQmkFirmwareVersion; + supportCodeEditing?: boolean; } ): Promise>; createBuildableFirmwareFile( diff --git a/src/store/state.ts b/src/store/state.ts index 507c4102..263cc8aa 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -218,10 +218,16 @@ export type IBuildableFirmwareCodeParameterValue = { definition: IBuildableFirmwareCodeParameter; }; +export type IBuildableFirmwareCodeValueType = 'parameters' | 'code'; + export type IBuildableFirmwareCodeParameterValueMap = { // File ID: Parameter Name : Parameter Value [fileId: string]: { - [parameterName: string]: IBuildableFirmwareCodeParameterValue; + type: IBuildableFirmwareCodeValueType; + parameters: { + [parameterName: string]: IBuildableFirmwareCodeParameterValue; + }; + code: string; }; }; diff --git a/src/utils/GoogleAnalytics.ts b/src/utils/GoogleAnalytics.ts index 096fe222..6fe78199 100644 --- a/src/utils/GoogleAnalytics.ts +++ b/src/utils/GoogleAnalytics.ts @@ -33,6 +33,7 @@ type IActionName = | 'docs/encoders' | 'docs/faq' | 'docs/review_policy' + | 'docs/support-code-editing' | 'docs/support-qmk-022' | 'docs/terms_of_use';