From 9ef33676eb7aef8715fa16b4238d251a683b04bb Mon Sep 17 00:00:00 2001 From: Khalid Elshafie Date: Fri, 18 Oct 2024 10:15:35 +1100 Subject: [PATCH] feat: add history (#29) Update the app to keep code history This PR: - Add code history to keep the last N executions - Bump bootstrap version from 4 to 5 - Persist theme selection in local storage - Introduce Modal component --- package-lock.json | 13 ++- package.json | 2 + public/index.html | 6 +- src/components/About/About.spec.tsx | 27 ++--- src/components/About/About.tsx | 109 +++++++----------- src/components/ActionButton/ActionButton.tsx | 6 + src/components/ActionButton/types.d.ts | 2 +- src/components/App/App.tsx | 3 + src/components/Header/Header.tsx | 13 ++- .../HistoryModal/HistoryModal.spec.tsx | 77 +++++++++++++ src/components/HistoryModal/HistoryModal.tsx | 80 +++++++++++++ src/components/HistoryModal/index.tsx | 1 + src/components/JsonView/JsonView.spec.tsx | 22 ++-- src/components/JsonView/JsonView.tsx | 45 +++----- src/components/Modal/Modal.tsx | 48 ++++++++ src/components/Modal/index.tsx | 1 + src/context/AppContext.tsx | 9 +- src/context/Reducer.spec.ts | 1 + src/context/Reducer.ts | 19 ++- src/context/types.d.ts | 1 + src/helpers/global.ts | 4 +- src/helpers/type.d.ts | 5 + src/services/storage.spec.ts | 1 + src/services/storage.ts | 34 ++++++ src/styles/App.css | 37 +++++- 25 files changed, 427 insertions(+), 139 deletions(-) create mode 100644 src/components/HistoryModal/HistoryModal.spec.tsx create mode 100644 src/components/HistoryModal/HistoryModal.tsx create mode 100644 src/components/HistoryModal/index.tsx create mode 100644 src/components/Modal/Modal.tsx create mode 100644 src/components/Modal/index.tsx diff --git a/package-lock.json b/package-lock.json index 3e089ae..a4d22b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "github-fork-ribbon-css": "^0.2.3", "lodash": "^4.17.21", "luxon": "^1.28.0", + "lz-string": "^1.5.0", "monaco-editor": "^0.34.0", "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", @@ -34,6 +35,7 @@ "@types/jest": "^26.0.15", "@types/lodash": "^4.14.185", "@types/luxon": "^3.0.1", + "@types/lz-string": "^1.5.0", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^18.0.6", @@ -2517,6 +2519,16 @@ "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", "dev": true }, + "node_modules/@types/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-s84fKOrzqqNCAPljhVyC5TjAo6BH4jKHw9NRNFNiRUY5QSgZCmVm5XILlWbisiKl+0OcS7eWihmKGS5akc2iQw==", + "deprecated": "This is a stub types definition. lz-string provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "lz-string": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -10397,7 +10409,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "bin": { "lz-string": "bin/bin.js" } diff --git a/package.json b/package.json index 75ba9a0..be63cc4 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "github-fork-ribbon-css": "^0.2.3", "lodash": "^4.17.21", "luxon": "^1.28.0", + "lz-string": "^1.5.0", "monaco-editor": "^0.34.0", "monaco-editor-webpack-plugin": "^7.0.1", "react": "^17.0.2", @@ -66,6 +67,7 @@ "@types/jest": "^26.0.15", "@types/lodash": "^4.14.185", "@types/luxon": "^3.0.1", + "@types/lz-string": "^1.5.0", "@types/node": "^12.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^18.0.6", diff --git a/public/index.html b/public/index.html index 2fc967e..6e48e5e 100644 --- a/public/index.html +++ b/public/index.html @@ -7,11 +7,13 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + ', () => { - it('render libraries list', () => { - render(); - const listElement = screen.getByTestId('about-libraries-list'); - expect(listElement.children.length).toEqual(LIBRARIES.length); - }); - - it('calls dipatch on button click', () => { - const state = { - display: 'block', - } as AppState; - const dispatch = jest.fn(); + const state = { + display: 'block', + } as AppState; + const dispatch = jest.fn(); + beforeEach(() => { render( - // eslint-disable-next-line react/jsx-no-constructed-context-values ); - fireEvent.click(screen.getByText(/close/i)); + }); + + it('render libraries list', () => { + const listElement = screen.getByTestId('about-libraries-list'); + expect(listElement.children.length).toEqual(LIBRARIES.length); + }); + + it('calls dipatch on button click', () => { + fireEvent.click(screen.getByTestId('modal-close-btn')); expect(dispatch).toHaveBeenCalledWith({ type: AppAactions.TOGGLE_ABOUT_MODAL, diff --git a/src/components/About/About.tsx b/src/components/About/About.tsx index 1003cc4..88de65f 100644 --- a/src/components/About/About.tsx +++ b/src/components/About/About.tsx @@ -2,84 +2,53 @@ import { useContext } from 'react'; import { LIBRARIES } from 'helpers/const'; import { AppContext } from 'context/AppContext'; import { AppAactions } from 'context/Reducer'; +import Modal from 'components/Modal'; const About: React.FC = () => { const { state, dispatch } = useContext(AppContext); + const open = state.display !== 'none'; + + const handleClose = () => { + dispatch({ type: AppAactions.TOGGLE_ABOUT_MODAL, payload: 'none' }); + }; + return ( -
-
-
-
-
-

About JS Playground

-
-
-

- JS Playground is an experimental JavaScript PlayGround created - for Education and Testing Purposes -

-
- This sandbox playground is hooked up directly with - -
-

Enjoy

- -
-
- -
-
+ + {lib.name} v{lib.version} + + Use as {lib.use} +
+ + ))} + +
+

Enjoy

+
+
+ + Khalid Elshafie +
-
+ ); }; diff --git a/src/components/ActionButton/ActionButton.tsx b/src/components/ActionButton/ActionButton.tsx index d2bd135..6bfdc88 100644 --- a/src/components/ActionButton/ActionButton.tsx +++ b/src/components/ActionButton/ActionButton.tsx @@ -11,6 +11,12 @@ const ButtonProps: Record = { className: 'btn btn-success', toolTip: 'Run Code (CtrCmd + k)', }, + history: { + title: 'History', + icon: 'fas fa-history', + className: 'btn btn-warning', + toolTip: 'Show run history', + }, }; const ActionButton: React.FC = ({ diff --git a/src/components/ActionButton/types.d.ts b/src/components/ActionButton/types.d.ts index f547c9e..62b5fab 100644 --- a/src/components/ActionButton/types.d.ts +++ b/src/components/ActionButton/types.d.ts @@ -1,4 +1,4 @@ -type ActionButtonType = 'execute' | 'clear'; +type ActionButtonType = 'execute' | 'clear' | 'history'; interface ActionButtonProps { type: ActionButtonType; diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 5e0bbf5..db2b99e 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -7,6 +7,7 @@ import ContextMenu from 'components/ContextMenu'; import JsonView from 'components/JsonView'; import CodeEditor from 'components/CodeEditor'; import Console from 'components/Console'; +import HistoryModal from 'components/HistoryModal'; const App: React.FC = () => { const { dispatch } = useContext(AppContext); @@ -52,6 +53,8 @@ const App: React.FC = () => { setPosition(null); }} /> + + ); }; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index a972923..359e716 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -26,13 +26,13 @@ const Header: React.FC = () => { }; return ( -