diff --git a/web/package.json b/web/package.json index f4e60b674..b3d553d48 100644 --- a/web/package.json +++ b/web/package.json @@ -16,7 +16,8 @@ "react-modal": "^3.11.1", "react-scripts": "3.3.0", "socket.io-client": "^2.3.0", - "styled-components": "^4.4.1" + "styled-components": "^4.4.1", + "uuid": "^3.4.0" }, "scripts": { "start": "cross-env PORT=5000 react-scripts start", diff --git a/web/src/App.js b/web/src/App.js index b540b3eec..88ba266a3 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,7 +1,7 @@ import React from 'react'; import './index.css'; import Screen from './Screen'; -import { CurrentWidgetProvider, CurrentDatabaseProvider } from './utility/globalState'; +import { CurrentWidgetProvider, CurrentDatabaseProvider, OpenedFilesProvider } from './utility/globalState'; import { SocketProvider } from './utility/SocketProvider'; function App() { @@ -9,7 +9,9 @@ function App() { - + + + diff --git a/web/src/FilesTabsPanel.js b/web/src/FilesTabsPanel.js index 33c76f72b..c0a678942 100644 --- a/web/src/FilesTabsPanel.js +++ b/web/src/FilesTabsPanel.js @@ -3,12 +3,13 @@ import styled from 'styled-components'; import theme from './theme'; import { TableIcon } from './icons'; +import { useOpenedFiles, useSetOpenedFiles } from './utility/globalState'; -const files = [ - { name: 'app.js' }, - { name: 'BranchCategory', type: 'table', selected: true }, - { name: 'ApplicationList' }, -]; +// const files = [ +// { name: 'app.js' }, +// { name: 'BranchCategory', type: 'table', selected: true }, +// { name: 'ApplicationList' }, +// ]; const FileTabItem = styled.div` border-right: 1px solid white; @@ -30,10 +31,31 @@ const FileNameWrapper = styled.span` `; export default function FilesTabsPanel() { + const files = useOpenedFiles(); + const setOpenedFiles = useSetOpenedFiles(); + + const handleTabClick = id => { + setOpenedFiles(files => + files.map(x => ({ + ...x, + selected: x.id == id, + })) + ); + }; + const handleMouseUp = (e, id) => { + if (e.button == 1) { + setOpenedFiles(files => files.filter(x => x.id != id)); + } + }; return ( <> {files.map(file => ( - + handleTabClick(file.id)} + onMouseUp={e => handleMouseUp(e, file.id)} + > {file.name} diff --git a/web/src/appobj/AppObjectList.js b/web/src/appobj/AppObjectList.js index d6532c988..45fc88e55 100644 --- a/web/src/appobj/AppObjectList.js +++ b/web/src/appobj/AppObjectList.js @@ -2,11 +2,14 @@ import React from 'react'; import styled from 'styled-components'; import { showMenu } from '../modals/DropDownMenu'; import { AppObjectCore } from './AppObjects'; +import { useSetOpenedFiles } from '../utility/globalState'; export function AppObjectList({ list, makeAppObj, SubItems = undefined, onObjectClick = undefined }) { + const setOpenedFiles = useSetOpenedFiles(); return (list || []).map(x => { - const appobj = makeAppObj(x); - let res = ; + const appobj = makeAppObj(x, { setOpenedFiles }); + if (onObjectClick) appobj.onClick = onObjectClick; + let res = ; if (SubItems) { res = ( <> diff --git a/web/src/appobj/AppObjects.js b/web/src/appobj/AppObjects.js index a0b1b0ab3..08906e89d 100644 --- a/web/src/appobj/AppObjects.js +++ b/web/src/appobj/AppObjects.js @@ -1,6 +1,7 @@ import React from 'react'; import styled from 'styled-components'; import { showMenu } from '../modals/DropDownMenu'; +import { useSetOpenedFiles } from '../utility/globalState'; const AppObjectDiv = styled.div` padding: 5px; @@ -34,6 +35,7 @@ export function AppObjectCore({ title, Icon, Menu, data, makeAppObj, onClick }) } export function AppObjectControl({ data, makeAppObj }) { - const appobj = makeAppObj(data); + const setOpenedFiles = useSetOpenedFiles(); + const appobj = makeAppObj(data, { setOpenedFiles }); return ; } diff --git a/web/src/appobj/tableAppObject.js b/web/src/appobj/tableAppObject.js index d4a482ed9..351189947 100644 --- a/web/src/appobj/tableAppObject.js +++ b/web/src/appobj/tableAppObject.js @@ -1,4 +1,5 @@ import React from 'react'; +import uuidv1 from 'uuid/v1'; import { TableIcon } from '../icons'; import { DropDownMenuItem } from '../modals/DropDownMenu'; import showModal from '../modals/showModal'; @@ -20,10 +21,14 @@ function Menu({ data, makeAppObj }) { ); } -export default function tableAppObject({ pureName, schemaName }) { +export default function tableAppObject({ pureName, schemaName }, { setOpenedFiles }) { const title = schemaName ? `${schemaName}.${pureName}` : pureName; const key = title; const Icon = TableIcon; + const onClick = ({ schemaName, pureName }) => { + const id = uuidv1(); + setOpenedFiles(files => [...files, { id, name: pureName }]); + }; - return { title, key, Icon, Menu }; + return { title, key, Icon, Menu, onClick }; } diff --git a/web/src/utility/globalState.js b/web/src/utility/globalState.js index 9cbe870c8..06ce73f9d 100644 --- a/web/src/utility/globalState.js +++ b/web/src/utility/globalState.js @@ -1,4 +1,5 @@ import React from 'react'; +import useStorage from './useStorage'; function createGlobalState(defaultValue) { const Context = React.createContext(null); @@ -19,8 +20,30 @@ function createGlobalState(defaultValue) { return [Provider, useValue, useSetValue]; } +function createStorageState(storageKey, defaultValue) { + const Context = React.createContext(null); + + function Provider({ children }) { + const [currentvalue, setCurrentValue] = useStorage(storageKey, localStorage, defaultValue); + return {children}; + } + + function useValue() { + return React.useContext(Context)[0]; + } + + function useSetValue() { + return React.useContext(Context)[1]; + } + + return [Provider, useValue, useSetValue]; +} + const [CurrentWidgetProvider, useCurrentWidget, useSetCurrentWidget] = createGlobalState('database'); export { CurrentWidgetProvider, useCurrentWidget, useSetCurrentWidget }; const [CurrentDatabaseProvider, useCurrentDatabase, useSetCurrentDatabase] = createGlobalState(null); export { CurrentDatabaseProvider, useCurrentDatabase, useSetCurrentDatabase }; + +const [OpenedFilesProvider, useOpenedFiles, useSetOpenedFiles] = createStorageState('openedFiles', []); +export { OpenedFilesProvider, useOpenedFiles, useSetOpenedFiles }; diff --git a/web/src/utility/useStorage.js b/web/src/utility/useStorage.js new file mode 100644 index 000000000..c81e2dbea --- /dev/null +++ b/web/src/utility/useStorage.js @@ -0,0 +1,36 @@ +import React from 'react'; + +export default function useStorage(key, storageObject, initialValue) { + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = React.useState(() => { + try { + // Get from local storage by key + const item = storageObject.getItem(key); + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue; + } catch (error) { + // If error also return initialValue + console.log(error); + return initialValue; + } + }); + + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = value => { + try { + // Allow value to be a function so we have same API as useState + const valueToStore = value instanceof Function ? value(storedValue) : value; + // Save state + setStoredValue(valueToStore); + // Save to local storage + storageObject.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + // A more advanced implementation would handle the error case + console.log(error); + } + }; + + return [storedValue, setValue]; +} diff --git a/web/yarn.lock b/web/yarn.lock index 09785eab6..92a16f566 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -10412,6 +10412,11 @@ uuid@^3.0.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== +uuid@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"