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
JavaScript
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