From 55d727b47149fb6725a6fc9a19647d46b5c076c1 Mon Sep 17 00:00:00 2001 From: whtv <28059402+whtv@users.noreply.github.com> Date: Sat, 25 Dec 2021 23:39:40 +0100 Subject: [PATCH 1/6] Implement system menu --- Platform/Client/httpInterface.js | 5 + Platform/UI/AppPostLoader.js | 9 ++ Platform/WebServer/css/menu.css | 94 ++++++++++++++++++ Platform/WebServer/index.html | 3 +- .../SystemActionFunctions.js | 45 +++++++++ .../Function-Libraries/SystemActionSwitch.js | 42 ++++++++ .../UI/Spaces/Design-Space/Workspace.js | 79 +++++++++++++++ Projects/ProjectsMenu.json | 99 +++++++++++++++++++ Projects/ProjectsSchema.json | 5 + 9 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 Platform/WebServer/css/menu.css create mode 100644 Projects/Foundations/UI/Function-Libraries/SystemActionFunctions.js create mode 100644 Projects/Foundations/UI/Function-Libraries/SystemActionSwitch.js create mode 100644 Projects/ProjectsMenu.json diff --git a/Platform/Client/httpInterface.js b/Platform/Client/httpInterface.js index 657a643e9c..8770c40800 100644 --- a/Platform/Client/httpInterface.js +++ b/Platform/Client/httpInterface.js @@ -2227,6 +2227,11 @@ exports.newHttpInterface = function newHttpInterface() { SA.projects.foundations.utilities.httpResponses.respondWithFile(path, httpResponse) } break + case 'ProjectsMenu': { + let path = global.env.PATH_TO_PROJECTS + '/' + 'ProjectsMenu.json' + SA.projects.foundations.utilities.httpResponses.respondWithFile(path, httpResponse) + } + break case 'ListSpaceFiles': { let fs = SA.nodeModules.fs let allFiles = [] diff --git a/Platform/UI/AppPostLoader.js b/Platform/UI/AppPostLoader.js index 9d5cccd813..7dfe23501b 100644 --- a/Platform/UI/AppPostLoader.js +++ b/Platform/UI/AppPostLoader.js @@ -24,6 +24,7 @@ function newAppPostLoader() { function onResponse(err, file) { UI.environment = JSON.parse(file) setupProjectsSchema() + setupProjectsMenu() } } @@ -36,6 +37,14 @@ function newAppPostLoader() { } } + function setupProjectsMenu() { + httpRequest(undefined, 'ProjectsMenu', onResponse) + + function onResponse(err, file) { + PROJECTS_MENU = JSON.parse(file) + } + } + function setupSchemas() { let totalWebServerCalls = 0 diff --git a/Platform/WebServer/css/menu.css b/Platform/WebServer/css/menu.css new file mode 100644 index 0000000000..2adf7ecb5d --- /dev/null +++ b/Platform/WebServer/css/menu.css @@ -0,0 +1,94 @@ +#topMenu { + position: fixed; + top: 0; + left: 200px; + z-index: 1; + font-family: 'Saira Condensed'; + font-weight: bold; + letter-spacing: 0.2px; +} + +nav il { + position: relative; + display: block; + opacity: 1; + cursor: pointer; +} + +nav > ul > il > a{ + background:rgb(0, 0, 0, 0); +} + +nav > ul > il > ul > il a{ + background:rgb(0, 0, 0, 0.5); +} + +nav il > ul { + padding: 0; + position: absolute; + pointer-events: none; +} + +nav > ul { + margin: 0; + display:flex; +} + +nav > ul > il { + pointer-events: all; + opacity: 1; + text-decoration: underline; +} + +ul il a { + white-space: nowrap; + display: block; +} + +il:hover > ul { + pointer-events: initial; +} + +il:hover > ul > il, +ul:hover > il { + opacity: 1; +} + +nav > ul > il il ul { + transform: translateX(100%); + top: 0; + right: 0; +} + +nav { + width: 80%; + margin: auto; +} + +nav a { + background:rgb(0, 0, 0, 0.5); + color:#FFF; + height: 38px; + margin: 0px 3px 3px 0px; + padding: 8px 10px; + box-sizing: border-box; + border-radius: 5px; + box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5); + position: relative; + transition: 0.1s; +} + +nav a:hover { + background:rgb(2 149 170); + transition: 0.1s; +} + +nav > ul > il > ul > il { + transition: opacity 0.1s; + opacity: 0; +} + +il > ul > il > ul > il { + transition: opacity 0.1s; + opacity: 0; +} diff --git a/Platform/WebServer/index.html b/Platform/WebServer/index.html index 5eca7795da..d9898606aa 100644 --- a/Platform/WebServer/index.html +++ b/Platform/WebServer/index.html @@ -13,6 +13,7 @@ + @@ -305,7 +306,7 @@ - +
\ No newline at end of file diff --git a/Projects/Foundations/UI/Function-Libraries/SystemActionFunctions.js b/Projects/Foundations/UI/Function-Libraries/SystemActionFunctions.js new file mode 100644 index 0000000000..cbb35cb30a --- /dev/null +++ b/Projects/Foundations/UI/Function-Libraries/SystemActionFunctions.js @@ -0,0 +1,45 @@ +function newFoundationsSystemActionFunctions() { + let thisObject = { + workspacesSubmenu: workspacesSubmenu + } + + return thisObject + + /* build submenus of all existing workspaces of every project for the system menu */ + async function workspacesSubmenu(type) { + let subMenu = [] + /* get the array of workspaces: [[…],[…],…] */ + let response = await httpRequestAsync(undefined, 'ListWorkspaces') + + if (type === 'plugin') { + /* for plugin workspaces, the first value of the workspace array is a non-empty string containing the project */ + let pluginWorkspaces = JSON.parse(response.message).filter(x => x[0] !== '') + let pluginWorkspaceProjects = [] + + for (let workspace of pluginWorkspaces) { + if (pluginWorkspaceProjects.includes(workspace[0])) { continue } + pluginWorkspaceProjects.push(workspace[0]) + } + /* for every project, collect the corresponding worspaces into a submenu and assign them the switchWorkspace action*/ + for (let project of pluginWorkspaceProjects) { + let projectSubmenuItem = {label: project, subMenu: []} + let projectPluginWorkspaces = JSON.parse(response.message).filter(x => x[0] === project) + for (let workspace of projectPluginWorkspaces) { + let label = workspace[1].replace('Plugin → ', '').replace('.json', '') + let action = {name: 'switchWorkspace', params: ['\'' + project + '\'', '\'' + label + '\'']} + projectSubmenuItem.subMenu.push({label: label, action: action}) + } + subMenu.push(projectSubmenuItem) + } + } else if (type === 'user') { + /* user workspaces have no associated project */ + let userWorkspaces = JSON.parse(response.message).filter(x => x[0] === '') + for (let workspace of userWorkspaces) { + let label = workspace[1].replace('.json', '') + let action = {name: 'switchWorkspace', params: ['""', '\'' + label + '\'']} + subMenu.push({label: label, action: action}) + } + } + return subMenu + } +} \ No newline at end of file diff --git a/Projects/Foundations/UI/Function-Libraries/SystemActionSwitch.js b/Projects/Foundations/UI/Function-Libraries/SystemActionSwitch.js new file mode 100644 index 0000000000..49788081a6 --- /dev/null +++ b/Projects/Foundations/UI/Function-Libraries/SystemActionSwitch.js @@ -0,0 +1,42 @@ +function newFoundationsSystemActionSwitch() { + + let thisObject = { + name: 'newFoundationsSystemActionSwitch', + executeAction: executeAction, + initialize: initialize, + finalize: finalize + } + + return thisObject + + function finalize() { + + } + + function initialize() { + /* Nothing to initialize since a Function Library does not hold any state. */ + } + + async function executeAction(action) { + switch (action.name) { + case 'saveWorkspace': + { + await UI.projects.foundations.spaces.designSpace.workspace.save() + } + break + case 'switchWorkspace': + { + UI.projects.foundations.spaces.designSpace.workspace.replaceWorkspaceByLoadingOne(action.params[0], action.params[1]) + } + break + /* the following is a special action called by the buildSystemMenu function that constructs and returns a submenu */ + case 'workspacesSubmenu': + { + return UI.projects.foundations.functionLibraries.systemActionFunctions.workspacesSubmenu(action.params[0]) + } + default: { + console.log("[WARN] Action sent to Foundations System Action Switch does not belong here. -> Action = " + action.name) + } + } + } +} diff --git a/Projects/Foundations/UI/Spaces/Design-Space/Workspace.js b/Projects/Foundations/UI/Spaces/Design-Space/Workspace.js index ad7c2d0c5b..16d854ee1e 100644 --- a/Projects/Foundations/UI/Spaces/Design-Space/Workspace.js +++ b/Projects/Foundations/UI/Spaces/Design-Space/Workspace.js @@ -50,6 +50,8 @@ function newWorkspace() { window.localStorage.setItem('Session Timestamp', sessionTimestamp) let actionSwitchesByProject = new Map() + let systemActionSwitchesByProject = new Map() + let topMenu = document.getElementById('topMenu') return thisObject @@ -75,6 +77,16 @@ function newWorkspace() { console.log('[WARN] Action Switch for project ' + project + ' not found.') } } + /* … and the system action switches map */ + for (let i = 0; i < PROJECTS_SCHEMA.length; i++) { + let project = PROJECTS_SCHEMA[i].name + try { + let systemActionSwitch = eval('new' + project.replaceAll('-', '') + 'SystemActionSwitch()') + systemActionSwitchesByProject.set(project, systemActionSwitch) + } catch (err) { + console.log('[WARN] System Action Switch for project ' + project + ' not found.') + } + } const browserURL = new URLSearchParams(window.location.search); const queryString = Object.fromEntries(browserURL.entries()); @@ -144,6 +156,8 @@ function newWorkspace() { //savingWorkspaceIntervalId = setInterval(saveWorkspace, 60000) UI.projects.foundations.utilities.statusBar.changeStatus("Displaying the UI...") + buildSystemMenu() + resolve() } } catch (err) { @@ -178,6 +192,69 @@ function newWorkspace() { } } } + + /* This function constructs the innerHTML of the topMenu div by following the system menu structure per-project defined in PROJECTS_MENU. + Only projects that have a project head node at the current workspace get a menu. */ + async function buildSystemMenu() { + let html = '