UNPKG

globular-mvc

Version:

Generic template to create web-application that made use of globular as backend and materialize as css (wrap in web-component's)

1,491 lines (1,234 loc) 125 kB
import { Model } from "../Model"; import { SlidePanel } from "./Slide" import '@polymer/paper-input/paper-input.js'; import '@polymer/paper-icon-button/paper-icon-button.js'; import { ApplicationView } from "../ApplicationView"; import { mergeTypedArrays, uint8arrayToStringMethod } from "../Utility"; import { v4 as uuidv4 } from "uuid"; import * as JwtDecode from "jwt-decode"; import { DeleteOneRqst, FindOneRqst, FindRqst, ReplaceOneRqst } from "globular-web-client/persistence/persistence_pb"; // The ace editor... import * as ace from 'brace'; import 'brace/mode/css'; import 'brace/mode/javascript'; import 'brace/mode/html'; import 'brace/theme/monokai'; import { setMoveable } from './moveable' import { setResizeable } from './rezieable' import { getCoords, randomUUID } from "./utility"; import * as getUuidByString from "uuid-by-string"; import * as cssbeautifier from "cssbeautifier" import htmlBeautify from 'html-beautify' import jsBeautify from 'js-beautify' import nodeToDataURL from 'html-element-to-image' import { GetThumbnailsRequest, GetThumbnailsResponse, GetVideoConversionLogsRequest } from "globular-web-client/file/file_pb"; import { DeleteDocumentRequest, IndexJsonObjectRequest } from "globular-web-client/search/search_pb"; import { PermissionManager } from "../Permission"; import { Application } from "../Application"; import { SubjectType } from "globular-web-client/rbac/rbac_pb"; import domtoimage from 'dom-to-image'; import { timeSince } from "./BlogPost"; import { RemoveGroupMemberAccountRqst } from "globular-web-client/resource/resource_pb"; // Test if the tag can hold text... function canHoldText(tagName) { let tags = [ "tt", "i", "b", "big", "small", "em", "strong", "dfn", "code", "samp", "kbd", "var", "cite", "abbr", "acronym", "sub", "sup", "span", "bdo", "address", "div", "a", "object", "p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "q", "ins", "del", "dt", "dd", "li", "label", "option", "textarea", "fieldset", "legend", "button", "caption", "td", "th", "title", "script", "style", "blockquote"] return tags.indexOf(tagName.toLowerCase()) != -1 } function getChildIndex(node) { return Array.prototype.indexOf.call(node.parentNode.childNodes, node); } // Return the css editor. function getCssEditor(style) { if (getWorkspace().querySelector("#" + style.id + "_css_editor") != null) { return getWorkspace().querySelector("#" + style.id + "_css_editor") } // Set the css from the editor... let css_editor = new CodeEditor("css", (evt) => { try { if (cssbeautifier(style.innerText) != css_editor.getText()) { Model.eventHub.publish("_need_save_event_", null, true) } // Here I will set the css text of the element... style.innerText = css_editor.getText() } catch (err) { style.innerText = css_editor.getText() } }, () => { // focus }, () => { // lost focus }) // Set the style to the editor... css_editor.setText(cssbeautifier(style.innerText)) if (style.name) css_editor.setTitle("CSS:" + style.name) else css_editor.setTitle("CSS:" + style.id.replace("_style", "")) return css_editor } // show the css editor. function showCssEditor(style) { // Show the css to edit the element style. let editor = getCssEditor(style) if (editor != null) { ApplicationView.layout.workspace().appendChild(editor) editor.style.zIndex = 1000; let editors = document.getElementsByTagName("globular-code-editor") for (var i = 0; i < editors.length; i++) { editors[i].style.zIndex = 1 } editor.style.zIndex = 100 return } } /** * Display the javascript code editor. * @returns */ function showJavascriptEditor(script) { let javascript_editor = getJavascriptEditor(script) if (javascript_editor != null) { ApplicationView.layout.workspace().appendChild(javascript_editor) javascript_editor.style.zIndex = 1000; let editors = document.getElementsByTagName("globular-code-editor") for (var i = 0; i < editors.length; i++) { editors[i].style.zIndex = 1 } javascript_editor.style.zIndex = 100 return } } function getJavascriptEditor(script) { if (getWorkspace().querySelector("#" + script.id + "_js_editor") != null) { return getWorkspace().querySelector("#" + style.id + "_js_editor") } // Set the css from the editor... let javascript_editor = new CodeEditor("javascript", (evt) => { try { if (jsBeautify(script.innerText) != javascript_editor.getText()) { script.reload = true // mark it to be reload at save time... Model.eventHub.publish("_need_save_event_", null, true) } // set the script script.innerText = javascript_editor.getText().split("<br>").join("") } catch (err) { script.innerText = javascript_editor.getText().split("<br>").join("") } }, () => { // on editor focus }, () => { // on editor lost focus }) // set the editor id javascript_editor.id = script.id + "_js_editor" // Set the style to the editor... javascript_editor.setText(jsBeautify(script.innerText)) if (script.name) javascript_editor.setTitle("JS:" + script.name) else javascript_editor.setTitle("JS:" + script.id.replace("_script", "")) return javascript_editor } /** * Show html editor. * @returns */ function showHtmlEditor(element) { let html_editor = getHtmlEditor(element) if (html_editor != null) { ApplicationView.layout.workspace().appendChild(html_editor) html_editor.style.zIndex = 1000; let editors = document.getElementsByTagName("globular-code-editor") for (var i = 0; i < editors.length; i++) { editors[i].style.zIndex = 1 } html_editor.style.zIndex = 100 return } } function getHtmlEditor(element) { // be sure that the element reference is up to date. if (getWorkspace().querySelector("#" + element.id + "_html_editor") != null) { return getWorkspace().querySelector("#" + element.id + "_html_editor") } // Set the css from the editor... let html_editor = new CodeEditor("html", (evt) => { // Test if the innerHTML has change... try { if (htmlBeautify(element.outerHTML) != html_editor.getText()) { Model.eventHub.publish("_need_save_event_", null, true) } // get active editor... let editors = [] let getEditors = (e) => { if (e.editor != undefined) { editors.push(e.editor) } for (var i = 0; i < e.children.length; i++) { getEditors(e.children[i]) } } getEditors(element) // set the innerHTML value. The element object will be lost... element.outerHTML = html_editor.getText().replace(/(\r\n|\n|\r)/gm, "").trim() // that will flush the actual element... element = document.getElementById(element.id) // So I will set back new element object in it editor. editors.forEach(editor => { // Set element editor.element = document.getElementById(editor.element.id) // refresh the element reference in the editor. editor.element.editor = editor // set back the editor position... editor.setContainerPosition() editor.setElementEvents() }) } catch (err) { // script.innerText = javascript_editor.getText().split("<br>").join("") } }, () => { element.editor.emphasis() }, () => { element.editor.de_emphasis() }) // Set the style to the editor... html_editor.id = element.id + "_html_editor" html_editor.setText(htmlBeautify(element.outerHTML)) //html_editor.setText(element.outerHTML) html_editor.setTitle("HTML " + element.id) return html_editor } function getWorkspace() { return document.getElementsByTagName("globular-workspace")[0];//document.body } /** * * @returns The active page, editor must be on the active page. */ export function getActiveWebPage() { return getWorkspace().getElementsByTagName("globular-web-page")[0] } /** * The content creator, it control edit mode and page creation. */ export class ContentManager extends HTMLElement { // attributes. // Create the applicaiton view. constructor() { super() // Set the shadow dom. this.attachShadow({ mode: 'open' }); // Innitialisation of the element. this.shadowRoot.innerHTML = ` <style> #container{ display: flex; } .vertical{ flex-direction: column-reverse; margin-left: 10px; border-bottom: 1px solid var(--palette-divider); margin-bottom: 15px; } .horizontal{ align-items: center; flex-grow: 1; border-right: 1px solid var(--palette-divider); margin-left: 10px; padding-left: 10px; margin-right: 10px; padding-right: 10px; } #toolbar { display: flex; align-items: center; } #edit-create-css-style-btn{ position: relative; } @media (max-width: 1024px) { #toolbar { display: none; } } #set-create-mode-btn:hover{ cursor: pointer; } paper-tooltip { position: absolute; top: 40px; } paper-button { font-size: 1rem; } </style> <div id="container"> <slot></slot> <div id="toolbar"> <div style="position: relative;"> <paper-button id="edit-create-js-style-btn" icon="icons:create">JS</paper-button> <paper-tooltip for="edit-create-js-style-btn" role="tooltip" tabindex="-1">Application Scripts</paper-tooltip> <slot name="javascript-manager"/> </div> <div style="position: relative;"> <paper-button id="edit-create-css-style-btn" icon="icons:create">CSS</paper-button> <paper-tooltip for="edit-create-css-style-btn" role="tooltip" tabindex="-1">Application Styles</paper-tooltip> <slot name="css-manager"/> </div> <div style="position: relative;"> <paper-icon-button id="create-page-btn" icon="icons:note-add"></paper-icon-button> <paper-tooltip for="create-page-btn" role="tooltip" tabindex="-1">Create Web Page</paper-tooltip> </div> <div style="position: relative;"> <paper-icon-button id="set-create-mode-btn" icon="icons:create"></paper-icon-button> <paper-tooltip for="set-create-mode-btn" role="tooltip" tabindex="-1">Enter Edit Mode</paper-tooltip> </div> <div style="position: relative;"> <paper-icon-button id="save-all-btn" icon="icons:save"></paper-icon-button> <paper-tooltip for="save-all-btn" role="tooltip" tabindex="-1">Save All Change</paper-tooltip> </div> </div> </div> ` // The style manager. this.styleManager = new CodeManager("css") this.styleManager.slot = "css-manager" this.appendChild(this.styleManager) this.scriptManager = new CodeManager("javascript") this.scriptManager.slot = "javascript-manager" this.appendChild(this.scriptManager) // only display tool if the user is allowed... this.toolbar = this.shadowRoot.querySelector("#toolbar") this.toolbar.parentNode.removeChild(this.toolbar) } init() { this.navigation = new Navigation() this.appendChild(this.navigation) this.navigation.init() // load style... this.loadStyles(styles => { styles.forEach(s => { let style = document.createElement("style") style.id = s.id style.name = s.name style.innerText = s.text ApplicationView.layout.workspace().appendChild(style) }) // load scripts this.loadScripts(scripts => { scripts.forEach(s => { let style = document.createElement("script") style.id = s.id style.name = s.name style.innerText = s.text ApplicationView.layout.workspace().appendChild(style) }) }, err => ApplicationView.displayMessage(err, 3000)) }, err => ApplicationView.displayMessage(err, 3000)) // init stuff. if (this.needSaveEventListener == null) Model.eventHub.subscribe("_need_save_event_", uuid => this.needSaveEventListener = uuid, evt => { let saveAllBtn = this.shadowRoot.querySelector("#save-all-btn") saveAllBtn.removeAttribute("disable") saveAllBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-text-primary)") window.dispatchEvent(new Event('resize')); // set need save... getActiveWebPage().needSave = true; }, true) } hasContent(){ let lnks = document.getElementsByTagName("globular-page-link") return lnks.length > 0 } setVertical() { this.shadowRoot.querySelector("#container").classList.add("vertical") this.shadowRoot.querySelector("#container").classList.remove("horizontal") this.navigation.setVertical() } setHorizontal() { this.shadowRoot.querySelector("#container").classList.add("horizontal") this.shadowRoot.querySelector("#container").classList.remove("vertical") this.navigation.setHorizontal() } // Display the toolbar... setEditMode() { this.shadowRoot.querySelector("#container").appendChild(this.toolbar) let setCreateModeBtn = this.shadowRoot.querySelector("#set-create-mode-btn") let createPageBtn = this.shadowRoot.querySelector("#create-page-btn") let saveAllBtn = this.shadowRoot.querySelector("#save-all-btn") let createEditCss = this.shadowRoot.querySelector("#edit-create-css-style-btn") let createEditJs = this.shadowRoot.querySelector("#edit-create-js-style-btn") setCreateModeBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-action-disabled)") createPageBtn.style.display = "none" saveAllBtn.style.display = "none" createEditCss.style.display = "none" createEditJs.style.display = "none" saveAllBtn.setAttribute("disable") saveAllBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-action-disabled)") setCreateModeBtn.onclick = () => { let highlighted = document.getElementsByClassName("highlighted") for (var i = 0; i < highlighted.length; i++) { if (highlighted[i].lowlight) highlighted[i].lowlight(); } if (createPageBtn.style.display == "none") { setCreateModeBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-text-primary)") createPageBtn.style.display = "block" createEditCss.style.display = "block" createEditJs.style.display = "block" saveAllBtn.style.display = "block" Model.eventHub.publish("_set_content_edit_mode_", true, true) this.navigation.edit = true } else { setCreateModeBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-action-disabled)") createPageBtn.style.display = "none" saveAllBtn.style.display = "none" createEditCss.style.display = "none" createEditJs.style.display = "none" Model.eventHub.publish("_set_content_edit_mode_", false, true) this.navigation.edit = false } } // Display styles... createEditCss.onclick = (evt) => { evt.stopPropagation() this.styleManager.show() } createEditJs.onclick = (evt) => { evt.stopPropagation() this.scriptManager.show() } // Save all... saveAllBtn.onclick = () => { if (saveAllBtn.hasAttribute("disable")) { return // nothing to save... } // Save all pages at once. this.navigation.savePages() // Delete style... let deleteStyle = (style) => { // delete the page. this.deleteStyle(style, () => { globular - content - manager if (this.styleManager.toDelete.length > 0) { let style = this.styleManager.toDelete.pop() deleteStyle(style) } }, err => { ApplicationView.displayMessage(err, 300); // also try to delete the page. if (this.styleManager.toDelete.length > 0) { let style = this.styleManager.toDelete.pop() deleteStyle(style) } }) } let deleteScript = (script) => { // delete the page. this.deleteScript(script, () => { if (this.scriptManager.toDelete.length > 0) { let script = this.scriptManager.toDelete.pop() deleteScript(script) } }, err => { ApplicationView.displayMessage(err, 300); // also try to delete the page. if (this.scriptManager.toDelete.length > 0) { let script = this.scriptManager.toDelete.pop() deleteScript(script) } }) } // I will save all style... this.saveStyles(() => { this.saveScripts(() => { // Delete script and style as needed... if (this.scriptManager.toDelete.length > 0) { this.deleteScript(this.scriptManager.toDelete.pop(), () => { if (this.styleManager.toDelete.length > 0) { this.deleteStyle(this.styleManager.toDelete.pop(), () => { }, err => ApplicationView.displayMessage(err, 3000)) } else { console.log("all scipt are delete...") } }, err => ApplicationView.displayMessage(err, 3000)) } else if (this.styleManager.toDelete.length > 0) { this.deleteStyle(this.styleManager.toDelete.pop(), () => { console.log("all style are delete...") }, err => ApplicationView.displayMessage(err, 3000)) } }) }) saveAllBtn.setAttribute("disable") saveAllBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-action-disabled)") } createPageBtn.onclick = () => { Model.eventHub.publish("_create_page_event_", {}, true) } } // Enter edit mode... enterEditMode() { let setCreateModeBtn = this.shadowRoot.querySelector("#set-create-mode-btn") setCreateModeBtn.click() } // Load existing css styles... loadStyles(callback, errorCallback) { let rqst = new FindRqst(); // set connection infos. let db = Model.application + "_db"; rqst.setId(Model.application); rqst.setDatabase(db); rqst.setCollection("Styles"); rqst.setQuery("{}"); let stream = Model.getGlobule(Model.address).persistenceService.find(rqst, { application: Model.application, domain: Model.domain }); let data = []; stream.on("data", (rsp) => { data = mergeTypedArrays(data, rsp.getData()); }); stream.on("status", (status) => { if (status.code == 0) { uint8arrayToStringMethod(data, (str) => { callback(JSON.parse(str)); }); } else { // In case of error I will return an empty array errorCallback(status.details) } }); } // save all styles element present in the workspace... saveStyles(callback) { // Get immediate style elements. let styles_ = ApplicationView.layout.workspace().querySelectorAll("style") let styles = [] for (var i = 0; i < styles_.length; i++) { let style = styles_[i] if (style.parentNode == ApplicationView.layout.workspace()) { styles.push(style) } } let saveStyle = (index) => { let style = styles[index] // I will put the style content into the style... let str = JSON.stringify({ id: style.id, name: style.name, text: style.innerText }) // save the user_data let rqst = new ReplaceOneRqst(); let db = Model.application + "_db"; // set the connection infos, rqst.setId(Model.application); rqst.setDatabase(db); let collection = "Styles"; // save only user data and not the how user info... rqst.setCollection(collection); rqst.setQuery(`{"_id":"${style.id}"}`); rqst.setValue(str); rqst.setOptions(`[{"upsert": true}]`); // So here I will set the address from the address found in the token and not // the address of the client itself. let token = localStorage.getItem("user_token") let domain = Application.account.domain // call persist data Model.getGlobule(domain).persistenceService .replaceOne(rqst, { token: token, application: Model.application, domain: domain }) .then((rsp) => { index += 1 if (index < styles.length) { saveStyle(index) } else { callback() } }) .catch((err) => { console.log(err) index += 1 // try to save next style if (index < styles.length) { saveStyle(index) } else { callback() } }); } if (styles.length > 0) { saveStyle(0) } else { callback() } } deleteStyle(style, callback, errorCallback) { // save the user_data let rqst = new DeleteOneRqst(); let db = Model.application + "_db"; // set the connection infos, rqst.setId(Model.application); rqst.setDatabase(db); let collection = "Styles"; // save only user data and not the how user info... rqst.setCollection(collection); rqst.setQuery(`{"_id":"${style.id}"}`); // So here I will set the address from the address found in the token and not // the address of the client itself. let token = localStorage.getItem("user_token") let decoded = JwtDecode(token); let domain = Application.account.domain // call persist data Model.getGlobule(domain).persistenceService .deleteOne(rqst, { token: token, application: Model.application, domain: domain }) .then((rsp) => { // Here I will return the value with it callback(this); }) .catch((err) => { errorCallback(err); }); } // Load existing css scripts... loadScripts(callback, errorCallback) { let rqst = new FindRqst(); // set connection infos. let db = Model.application + "_db"; rqst.setId(Model.application); rqst.setDatabase(db); rqst.setCollection("Scripts"); rqst.setQuery("{}"); let stream = Model.getGlobule(Model.address).persistenceService.find(rqst, { application: Model.application, domain: Model.domain }); let data = []; stream.on("data", (rsp) => { data = mergeTypedArrays(data, rsp.getData()); }); stream.on("status", (status) => { if (status.code == 0) { uint8arrayToStringMethod(data, (str) => { callback(JSON.parse(str)); }); } else { // In case of error I will return an empty array errorCallback(status.details) } }); } // save all scripts element present in the workspace... saveScripts(callback) { // Get immediate style elements. let scripts_ = ApplicationView.layout.workspace().querySelectorAll("script") let scripts = [] for (var i = 0; i < scripts_.length; i++) { let s = scripts_[i] if (s.parentNode == ApplicationView.layout.workspace()) { scripts.push(s) } } let saveScript = (index) => { let s = scripts[index] // I will put the style content into the style... let str = JSON.stringify({ id: s.id, name: s.name, text: s.innerText }) // save the user_data let rqst = new ReplaceOneRqst(); let db = Model.application + "_db"; // set the connection infos, rqst.setId(Model.application); rqst.setDatabase(db); let collection = "Scripts"; // save only user data and not the how user info... rqst.setCollection(collection); rqst.setQuery(`{"_id":"${s.id}"}`); rqst.setValue(str); rqst.setOptions(`[{"upsert": true}]`); // So here I will set the address from the address found in the token and not // the address of the client itself. let token = localStorage.getItem("user_token") let decoded = JwtDecode(token); let domain = Application.account.domain // call persist data Model.getGlobule(domain).persistenceService .replaceOne(rqst, { token: token, application: Model.application, domain: domain }) .then((rsp) => { index += 1 if (index < scripts.length) { saveScript(index) } else { callback() } }) .catch((err) => { console.log(err) index += 1 // try to save next style if (index < scripts.length) { saveScript(index) } else { callback() } }); } if (scripts.length > 0) { saveScript(0) } else { callback() } } deleteScript(script, callback, errorCallback) { // save the user_data let rqst = new DeleteOneRqst(); let db = Model.application + "_db"; // set the connection infos, rqst.setId(Model.application); rqst.setDatabase(db); let collection = "Scripts"; // save only user data and not the how user info... rqst.setCollection(collection); rqst.setQuery(`{"_id":"${script.id}"}`); // So here I will set the address from the address found in the token and not // the address of the client itself. let token = localStorage.getItem("user_token") let domain = Application.account.domain // call persist data Model.getGlobule(domain).persistenceService .deleteOne(rqst, { token: token, application: Model.application, domain: domain }) .then((rsp) => { // Here I will return the value with it callback(this); }) .catch((err) => { errorCallback(err); }); } } customElements.define('globular-content-manager', ContentManager) /** * */ export class CodeManager extends HTMLElement { // attributes. // Create the applicaiton view. constructor(mode) { super() // Set the shadow dom. this.attachShadow({ mode: 'open' }); this.mode = mode // keep the list of style to be deleted... this.toDelete = [] // Innitialisation of the layout. this.shadowRoot.innerHTML = ` <style> paper-card{ background-color: var(--palette-background-paper); color: var(--palette-text-primary); } #container{ font-size: 1rem; display: flex; flex-direction: column; position: absolute; top: 64px; right: 0px; background-color: var(--palette-background-paper); color: var(--palette-text-primary); min-width: 290px; } #styles{ display: flex; align-items: center; flex-direction: column; text-transform: initial; } .element-lnk { position: relative; display: flex; align-items: center; width: 100%; transition: background 0.2s ease,padding 0.8s linear; background-color: var(--palette-background-paper); color: var(--palette-text-primary); } .element-lnk:hover{ filter: invert(10%); cursor: pointer; } paper-input { background: transparent; color: var(--palette-text-primary); } </style> <paper-card id="container"> <div style="display: flex; border-bottom: 1px solid var(--palette-divider);"> <paper-icon-button id="add-element-btn" icon="icons:add" class="btn_"></paper-icon-button> <paper-tooltip for="add-element-btn" role="tooltip" tabindex="-1">Add Style</paper-tooltip> <div id="create-element-div" style="display: none; flex-grow: 1;"> <paper-input style="display: flex;flex-grow: 1;" no-label-float></paper-input> </div> </div> <div style="max-height: 400px; overflow-y: auto"> <div id="content"> </div> </div> </paper-card> ` // The container. this.container = this.shadowRoot.querySelector("#container") this.container.onclick = (evt) => { evt.stopPropagation() } let addElementBtn = this.shadowRoot.querySelector("#add-element-btn") addElementBtn.onclick = (evt) => { evt.stopPropagation() this.addCodeElement() } this.parent = null this.visible = false // Hide the document.body.addEventListener("click", (evt) => { if (this.visible) { var rectMenu = this.container.getBoundingClientRect(); var overMenu = evt.x > rectMenu.x && evt.x < rectMenu.right && evt.y > rectMenu.y && evt.y < rectMenu.bottom if (!overMenu) { this.hide() } } }) } connectedCallback() { // keep the parent in variable. this.parent = this.parentNode if (!this.visible) { this.hide() } this.displayContent() } show() { if (this.parent) { if (!this.visible) { let codeManagers = document.getElementsByTagName("globular-code-manager") for (var i = 0; i < codeManagers.length; i++) { codeManagers[i].hide() } this.visible = true this.parent.appendChild(this) } } } hide() { if (this.parentNode) { this.visible = false this.parentNode.removeChild(this) } } // Display list of existing script... displayContent() { // display style or script depending of the mode. let tagName = "" if (this.mode == "css") { tagName = "style" } else if (this.mode == "javascript") { tagName = "script" } let div = this.shadowRoot.querySelector("#content") div.innerHTML = ""; // workspace must be active. if (!ApplicationView.layout.workspace()) { return } // Get style list. let elements_ = ApplicationView.layout.workspace().querySelectorAll(tagName) let elements = [] for (var i = 0; i < elements_.length; i++) { let e = elements_[i] if (e.parentNode == ApplicationView.layout.workspace()) { elements.push(e) } } for (var i = 0; i < elements.length; i++) { let e = elements[i] if (e.parentNode == ApplicationView.layout.workspace()) { let html = ` <div class="element-lnk"> <paper-icon-button id="edit-${e.id}-btn" icon="icons:create" class="btn_"></paper-icon-button> <span id="edit-${e.id}-lnk" style="flex-grow: 1; padding-left: 16px;">${e.name}</span> <paper-icon-button id="delete-${e.id}-btn" icon="icons:delete" class="btn_"></paper-icon-button> <paper-ripple> </paper-ripple> </div> ` let range = document.createRange() div.appendChild(range.createContextualFragment(html)) let editLnk = div.querySelector(`#edit-${e.id}-lnk`) editLnk.onclick = () => { if (this.mode == "css") showCssEditor(e) else if (this.mode = "javascript") { showJavascriptEditor(e) } this.hide() } let deleteBtn = div.querySelector(`#delete-${e.id}-btn`) let editBtn = div.querySelector(`#edit-${e.id}-btn`) editBtn.onclick = (evt) => { // remove the editLnk let parent = editLnk.parentNode parent.removeChild(editLnk) editBtn.setAttribute("disable") editBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-action-disabled)") // Create the input to set the new name... let input = document.createElement("paper-input") input.setAttribute("no-label-float") parent.insertBefore(input, deleteBtn) input.value = e.name // put in edit mode and pre-select the text. setTimeout(() => { input.focus() input.inputElement.inputElement.select() }, 100) input.onkeyup = (evt) => { if (evt.code === 'Enter' || evt.code === "NumpadEnter" || evt.code === 'Escape') { if (evt.code != 'Escape') { let id = "" if (this.mode == "css") { id = "_" + getUuidByString(input.value + "_style") } else { id = "_" + getUuidByString(input.value + "_script") } if (document.getElementById(id)) { if (this.mode == "css") { ApplicationView.displayMessage("style with name " + input.value + " already exist!", 3000) } else { ApplicationView.displayMessage("script with name " + input.value + " already exist!", 3000) } // reselect the text... input.inputElement.inputElement.select() return } // create a copy of the node to delete at save... this.toDelete.push(e.cloneNode(true)) // set name... e.name = input.value e.id = id editLnk.innerText = e.name // set need save... Model.eventHub.publish("_need_save_event_", null, true) } editBtn.removeAttribute("disable") editBtn.style.setProperty("--iron-icon-fill-color", "var(--palette-text-primary)") parent.removeChild(input) parent.insertBefore(editLnk, deleteBtn) } } } deleteBtn.onclick = () => { // remove the element menu deleteBtn.parentNode.parentNode.removeChild(deleteBtn.parentNode) // remove the element itself... e.parentNode.removeChild(e) // mark the element to be deleted in the next save. this.toDelete.push(e) Model.eventHub.publish("_need_save_event_", null, true) } } } } // Add a new css style or js script element addCodeElement() { let div = this.shadowRoot.querySelector("#create-element-div") div.style.display = "flex" let input = div.querySelector("paper-input") setTimeout(() => { input.focus() }, 100) input.onkeyup = (evt) => { evt.stopPropagation(); if (evt.code === 'Enter' || evt.code === "NumpadEnter") { console.log("Enter key press!") let name = input.value; let id = "" // if code is css style... if (this.mode == "css") { id = "_" + getUuidByString(name + "_style") if (ApplicationView.layout.workspace().querySelector("#" + id)) { ApplicationView.displayMessage("A style named " + name + " already exist!") return } input.value = "" div.style.display = "none" // So here I will create style and put in the workspace... let style = document.createElement("style") style.id = id style.name = name // append the style in the workspace. ApplicationView.layout.workspace().appendChild(style) // refresh the style list... this.displayContent() } else if (this.mode == "javascript") { id = "_" + getUuidByString(name + "_script") // Here the code is javascript. if (ApplicationView.layout.workspace().querySelector("#" + id)) { ApplicationView.displayMessage("A script named " + name + " already exist!") return } input.value = "" div.style.display = "none" // So here I will create style and put in the workspace... let script = document.createElement("script") script.id = id script.name = name // append the style in the workspace. ApplicationView.layout.workspace().appendChild(script) // refresh the style list... this.displayContent() } Model.eventHub.publish("_need_save_event_", null, true) return } else if (evt.code === 'Escape') { input.value = "" input.blur() div.style.display = "none" return } } } } customElements.define('globular-code-manager', CodeManager) /** * Contain the navigation panel */ export class Navigation extends HTMLElement { // attributes. // Create the applicaiton view. constructor() { super() // Set the shadow dom. this.attachShadow({ mode: 'open' }); // Innitialisation of the element. this.shadowRoot.innerHTML = ` <style> #container { display: flex; user-select: none; -moz-user-select: none; -khtml-user-select: none; -webkit-user-select: none; -o-user-select: none; } ::slotted(.drop-zone) { background-color: transparent; width: 20px; } </style> <div id="container"> <slot></slot> </div> ` // Connect to event this.set_content_edit_mode_listener = null this.create_page_listener = null // keep page to be deleted... this.toDelete = [] this.edit = false; this.dragging = false; } init() { // set|reset edit mode if (!this.set_content_edit_mode_listener) Model.eventHub.subscribe("_set_content_edit_mode_", uuid => this.set_content_edit_mode_listener = uuid, evt => this.setEditMode(evt), true) // create a new page if (!this.create_page_listener) Model.eventHub.subscribe("_create_page_event_", uuid => this.create_page_listener = uuid, evt => this.createPage(), true) // delete page event. if (!this.delete_page_listener) Model.eventHub.subscribe("_delete_web_page_", uuid => this.delete_page_listener = uuid, page => { console.log("delete page...", page) this.toDelete.push(page) // so here I will delete the page lnk... let lnk = page.link this.removeChild(lnk) let lnks = document.getElementsByTagName("globular-page-link") for (var i = 0; i < lnks.length; i++) { lnks[i].webPage.index = i; } if (lnks.length > 0) { if (lnk.webPage.index == 0) { lnks[0].webPage.setPage() } else { lnks[lnk.webPage.index - 1].webPage.setPage() } } }, true) // The set page event... if (!this.set_page_listener) Model.eventHub.subscribe("_set_web_page_", uuid => this.set_page_listener = uuid, page => { let lnks = document.getElementsByTagName("globular-page-link") for (var i = 0; i < lnks.length; i++) { lnks[i].de_emphasis() } if (lnks.length > 0) { lnks[page.index].emphasis() } }, true) // Init the webpages... ApplicationView.wait(`<div style="display: flex; flex-direction: column; justify-content: center;"><span>load webpages</span><span>please wait</span><span>...</span></div>`) this.loadWebPages(pages => { ApplicationView.resume() pages = pages.sort((a, b) => (a.index > b.index) ? 1 : ((b.index > a.index) ? -1 : 0)) // Test if the element is in the context... for (var i = 0; i < pages.length; i++) { let page = new WebPage(pages[i]._id, pages[i].name, pages[i].style, pages[i].script, pages[i].index, pages[i].elements, pages[i].thumbnail) let lnk = new NavigationPageLink(page) this.appendLink(lnk) if (page.index == 0) { page.setPage() } } }, err => ApplicationView.displayMessage(err, 3000)); } setVertical() { this.shadowRoot.querySelector("#container").style.flexDirection = "column" } setHorizontal() { this.shadowRoot.querySelector("#container").style.flexDirection = "row" } // Retreive webpage content... loadWebPages(callback, errorCallback) { // Insert the notification in the db. let rqst = new FindRqst(); // set connection infos. let db = Model.application + "_db"; rqst.setId(Model.application); rqst.setDatabase(db); rqst.setCollection("WebPages"); rqst.setQuery("{}"); let stream = Model.getGlobule(Model.address).persistenceService.find(rqst, { application: Model.application, domain: Model.domain }); let data = []; stream.on("data", (rsp) => { data = mergeTypedArrays(data, rsp.getData()); }); stream.on("status", (status) => { if (status.code == 0) { uint8arrayToStringMethod(data, (str) => { callback(JSON.parse(str)); }); } else { // In case of error I will return an empty array errorCallback(status.details) ApplicationView.resume() } }); } // Set|Reset edit mode. setEditMode(edit) { let links = this.querySelectorAll("globular-page-link") for (var i = 0; i < links.length; i++) { links[i].edit = edit; if (edit) { // set the page edit mode. links[i].webPage.edit = true links[i].webPage.setEditMode() } else { links[i].resetEditMode() links[i].webPage.edit = false links[i].webPage.resetEditMode() } } } // Create page event. createPage() { let index = 0 let lnks = document.getElementsByTagName("globular-page-link") if (lnks) { index = lnks.length } let page = new WebPage("page_" + uuidv4(), "new page", "", "", index) let pageLnk = new NavigationPageLink(page) this.appendLink(pageLnk) pageLnk.setEditMode() getWorkspace().appendChild(page) page.setEditMode() } appendLink(lnk) { lnk.setAttribute("draggable", "true") // the mouvse down on the div lnk.ondragstart = (evt) => { if (this.edit) { lnk.style.cursor = "move"; this.dragging = true; evt.dataTransfer.setData("Text", lnk.id); } } // set back to default lnk.ondragend = () => { lnk.style.cursor = "default"; this.dragging = false; } let dropZones = this.querySelectorAll(".drop-zone") if (dropZones.length == 0) { // I will append the leading drop-zone. this.appendChild(this.createDropZone()) } this.appendChild(lnk) // append the drop zone af