UNPKG

ts-mind

Version:

A web-mind lib write in typescript.Sound apis and friendly plug-in mechanism in this lib.

554 lines (501 loc) 17.3 kB
import { $logger, $isEl, $elByID, $doc, $pushText, $pushChild } from "../../util/tools"; import { dom } from "../../util/dom"; import TSMind, { TSMindEventTypeMap } from ".."; import layout_provider from "./layout"; import { TSM_node } from "../node"; import { text } from "../../util/text"; import { _slice } from "../../util/array"; import { canvas } from "../../util/canvas"; import { TSM_Node_Names } from "../../util/constants"; interface ITSMViewProvOptions { [k: string]: any; } type ITSMViewEle<T = HTMLElement> = T | null; export default class view_provider { private tsm: TSMind; // public conf public opts: ITSMViewProvOptions; public layout: layout_provider; public container: HTMLElement; public e_panel: HTMLElement = $doc.createElement("div"); public e_nodes: HTMLElement = $doc.createElement(TSM_Node_Names.nodes); public e_canvas: HTMLCanvasElement = $doc.createElement("canvas"); public e_editor: HTMLInputElement = $doc.createElement("input"); public canvas_ctx: CanvasRenderingContext2D = this.e_canvas.getContext("2d") as CanvasRenderingContext2D; public size = { w: 0, h: 0 }; public selected_node: ITSMViewEle<TSM_node> = null; public editing_node: ITSMViewEle<TSM_node> = null; // view zoom private actualZoom = 1; private zoomStep = 0.1; private minZoom = 0.5; private maxZoom = 2; constructor(tsm: TSMind, options: ITSMViewProvOptions) { this.opts = options; this.tsm = tsm; this.layout = tsm.layout_provider; $logger.debug("view.init"); this.container = $isEl(this.opts.container) ? this.opts.container : $elByID(this.opts.container); if (!this.container) { $logger.error("the options.view.container was not be found in dom"); return; } this.e_panel.className = "jsmind-inner"; this.e_panel.appendChild(this.e_canvas); this.e_panel.appendChild(this.e_nodes); this.e_editor.className = "jsmind-editor"; this.e_editor.type = "text"; const v = this; dom.add_event(this.e_editor, "keydown", function(e) { const evt = e || event; if (evt.keyCode === 13) { v.edit_node_end(); evt.stopPropagation(); } }); dom.add_event(this.e_editor, "blur", function() { v.edit_node_end(); }); this.container.appendChild(this.e_panel); } add_event = (obj: {}, event_name: ITSMEventType, event_handle: ITSMAnyCall) => { this.e_nodes && dom.add_event(this.e_nodes, event_name, function(e) { const evt = e || event; event_handle.call(obj, evt); }); }; get_binded_nodeid(element: HTMLElement): null | string { if (element == null) { return null; } const tagName = element.tagName.toLowerCase(); if (tagName === TSM_Node_Names.nodes || tagName === "body" || tagName === "html") { return null; } if (tagName === TSM_Node_Names.node || tagName === TSM_Node_Names.fold) { return element.getAttribute("nodeid"); } else if (element.parentElement) { return this.get_binded_nodeid(element.parentElement); } return null; } is_expander = (element: HTMLElement) => { return element.tagName.toLowerCase() === TSM_Node_Names.fold; }; reset = () => { $logger.debug("view.reset"); this.selected_node = null; this.clear_lines(); this.clear_nodes(); this.reset_theme(); }; reset_theme = () => { const theme_name = this.tsm.options.theme; if (!!theme_name) { this.e_nodes!.className = "theme-" + theme_name; } else { this.e_nodes!.className = ""; } }; reset_custom_style = () => { const nodes = this.tsm.mind!.nodes; for (const nodeid in nodes) { if (nodeid) this.reset_node_custom_style(nodes[nodeid]); } }; load = () => { $logger.debug("view.load"); this.init_nodes(); }; expand_size = () => { const min_size = this.layout.get_min_size(); if (!min_size) return; const min_height = min_size.h + this.opts.vmargin * 2; const min_width = min_size.w + this.opts.hmargin * 2; let client_w = this.e_panel!.clientWidth || 0; let client_h = this.e_panel!.clientHeight || 0; if (client_w < min_width) { client_w = min_width; } if (client_h < min_height) { client_h = min_height; } this.size.w = client_w; this.size.h = client_h; }; init_nodes_size = (node: TSM_node) => { if (!node.view_data.element) return; node.view_data.width = node.view_data.element.clientWidth; node.view_data.height = node.view_data.element.clientHeight; }; init_nodes = () => { const nodes = this.tsm.mind!.nodes; const doc_frag = $doc.createDocumentFragment(); for (const nodeid in nodes) { if (nodeid) this.create_node_element(nodes[nodeid], doc_frag); } this.e_nodes!.appendChild(doc_frag); for (const nodeid in nodes) { if (nodeid) this.init_nodes_size(nodes[nodeid]); } }; add_node = (node: TSM_node) => { this.create_node_element(node, this.e_nodes as HTMLElement); this.init_nodes_size(node); }; create_node_element = (node: TSM_node, parent_node: HTMLElement | DocumentFragment) => { const d = $doc.createElement(TSM_Node_Names.node); if (node.isroot) { d.className = "root"; // d.style.visibility = "visible"; } else { const d_e = $doc.createElement(TSM_Node_Names.fold); $pushText(d_e, "-"); d_e.setAttribute("nodeid", node.id); d_e.style.visibility = "hidden"; parent_node.appendChild(d_e); node.view_data.expander = d_e; } if (!!node.topic) { if (this.opts.support_html) { $pushChild(d, node.topic); } else { $pushText(d, node.topic); } } d.setAttribute("nodeid", node.id); // d.style.visibility = "hidden"; this._reset_node_custom_style(d, node.data); parent_node.appendChild(d); node.view_data.element = d; }; remove_node = (node: TSM_node) => { if (this.selected_node != null && this.selected_node!.id === node.id) { this.selected_node = null; } if (this.editing_node !== null && this.editing_node.id === node.id && node.view_data.element) { node.view_data.element.removeChild(this.e_editor as HTMLInputElement); this.editing_node = null; } const children = node.children; let i = children.length; while (i--) { this.remove_node(children[i]); } const element = node.view_data.element; const expander = node.view_data.expander; if (this.e_nodes) { element && this.e_nodes.removeChild(element); expander && this.e_nodes.removeChild(expander); node.view_data.element = null; node.view_data.expander = null; } }; update_node = (node: TSM_node) => { const element = node.view_data.element as HTMLElement; if (!!node.topic) { if (this.opts.support_html) { $pushChild(element, node.topic); } else { $pushText(element, node.topic); } } node.view_data.width = element.clientWidth; node.view_data.height = element.clientHeight; }; select_node = (node: null | TSM_node) => { if (!node || !this.selected_node) return; const selected_node_ele = this.selected_node.view_data.element; const node_ele = node.view_data.element; if (!!selected_node_ele) { selected_node_ele.className = selected_node_ele.className.replace(/\s*selected\b/i, ""); this.reset_node_custom_style(this.selected_node as TSM_node); } if (!!node && !!node_ele) { this.selected_node = node; node_ele.className += " selected"; this.clear_node_custom_style(node); } }; select_clear = () => { this.select_node(null); }; get_editing_node = () => { return this.editing_node; }; is_editing = () => { return !!this.editing_node; }; edit_node_begin = (node: TSM_node) => { if (!node.topic) { $logger.warn("don't edit image nodes"); return; } if (this.editing_node != null) { this.edit_node_end(); } this.editing_node = node; const element = node.view_data.element; if (!element) return; const topic = node.topic; const ncs = getComputedStyle(element); this.e_editor.value = topic; this.e_editor.style.width = element.clientWidth - parseInt(ncs.getPropertyValue("padding-left"), 10) - parseInt(ncs.getPropertyValue("padding-right"), 10) + "px"; element.innerHTML = ""; element.appendChild(this.e_editor as Node); element.style.zIndex = "5"; this.e_editor!.focus(); this.e_editor!.select(); }; edit_node_end = () => { if (this.editing_node != null) { const node = this.editing_node; this.editing_node = null; const element = node.view_data.element as HTMLElement; const topic = this.e_editor!.value; element.style.zIndex = "auto"; element.removeChild(this.e_editor as Node); if (text.is_empty(topic) || node.topic === topic) { if (this.opts.support_html) { $pushChild(element, node.topic); } else { $pushText(element, node.topic); } } else { this.tsm.update_node(node.id, topic); } } }; get_view_offset = () => { const bounds = this.layout.bounds; return { x: (this.size.w - bounds.e - bounds.w) / 2, y: this.size.h / 2 }; }; resize = () => { this.e_canvas!.width = 1; this.e_canvas!.height = 1; this.e_nodes!.style.width = "1px"; this.e_nodes!.style.height = "1px"; this.expand_size(); this._show(); }; _show = () => { this.e_canvas!.width = this.size.w; this.e_canvas!.height = this.size.h; this.e_nodes!.style.width = this.size.w + "px"; this.e_nodes!.style.height = this.size.h + "px"; this.show_nodes(); this.show_lines(); // this.layout.cache_valid = true; this.tsm.invoke_event_handle(TSMindEventTypeMap.resize, { data: [] }); }; zoomIn = () => { return this.setZoom(this.actualZoom + this.zoomStep); }; zoomOut = () => { return this.setZoom(this.actualZoom - this.zoomStep); }; setZoom = (zoom: number) => { if (zoom < this.minZoom || zoom > this.maxZoom) { return false; } this.actualZoom = zoom; const _children = _slice.call(this.e_panel!.children); for (const child of _children) { child.style.transform = "scale(" + zoom + ")"; } this.show(true); return true; }; _center_root = () => { // center root node const outer_w = this.e_panel!.clientWidth; const outer_h = this.e_panel!.clientHeight; if (this.size.w > outer_w) { const _offset = this.get_view_offset(); this.e_panel!.scrollLeft = _offset.x - outer_w / 2; } if (this.size.h > outer_h) { this.e_panel!.scrollTop = (this.size.h - outer_h) / 2; } }; show = (keep_center: boolean) => { $logger.debug("view.show"); this.expand_size(); this._show(); if (keep_center) { this._center_root(); } }; relayout = () => { this.expand_size(); this._show(); }; save_location = (node: TSM_node) => { const vd = node.view_data; if (vd) vd._saved_location = { x: Number(vd.element!.style.left) - this.e_panel!.scrollLeft, y: Number(vd.element!.style.top) - this.e_panel!.scrollTop }; }; restore_location = (node: TSM_node) => { const vd = node.view_data; if (vd && vd.element) { const _ele = vd.element; this.e_panel!.scrollLeft = Number(_ele.style.left) - vd._saved_location!.x; this.e_panel!.scrollTop = Number(vd.element!.style.top) - vd._saved_location!.y; } }; clear_nodes = () => { const mind = this.tsm.mind; if (mind == null) { return; } const nodes = mind.nodes; let node = null; for (const nodeid in nodes) { if (!nodeid) continue; node = nodes[nodeid]; node.view_data.element = null; node.view_data.expander = null; } this.e_nodes!.innerHTML = ""; }; show_nodes = () => { const nodes = this.tsm.mind!.nodes; const _offset = this.get_view_offset(); for (const nodeid in nodes) { if (!nodeid) continue; const node = nodes[nodeid]; $logger.log(node); const node_element = node.view_data.element; const expander = node.view_data.expander; if (!node_element) continue; if (!this.layout.is_visible(node)) { node_element.style.display = "none"; expander!.style.display = "none"; continue; } this.reset_node_custom_style(node); const p = this.layout.get_node_point(node); node.view_data.abs_x = _offset.x + p.x; node.view_data.abs_y = _offset.y + p.y; node_element.style.left = node.view_data.abs_x + "px"; node_element.style.top = node.view_data.abs_y + "px"; node_element.style.display = ""; node_element.style.visibility = "visible"; // if (!node.isroot && node.children.length > 0) { const expander_text = node.expanded ? "-" : "+"; const p_expander = this.layout.get_expander_point(node); if (expander) { expander.style.left = _offset.x + p_expander.x + "px"; expander.style.top = _offset.y + p_expander.y + "px"; expander.style.display = ""; expander.style.visibility = "visible"; $pushText(expander, expander_text); } } // hide expander while all children have been removed if (!node.isroot && node.children.length === 0 && expander) { expander.style.display = "none"; expander.style.visibility = "hidden"; } } }; reset_node_custom_style = (node: TSM_node) => { this._reset_node_custom_style(node.view_data.element as HTMLElement, node.data); }; _reset_node_custom_style = (node_element: HTMLElement, node_data: any) => { if ("background-color" in node_data) { node_element.style.backgroundColor = node_data["background-color"]; } if ("foreground-color" in node_data) { node_element.style.color = node_data["foreground-color"]; } if ("width" in node_data) { node_element.style.width = node_data.width + "px"; } if ("height" in node_data) { node_element.style.height = node_data.height + "px"; } if ("font-size" in node_data) { node_element.style.fontSize = node_data["font-size"] + "px"; } if ("font-weight" in node_data) { node_element.style.fontWeight = node_data["font-weight"]; } if ("font-style" in node_data) { node_element.style.fontStyle = node_data["font-style"]; } if ("background-image" in node_data) { const backgroundImage = node_data["background-image"] as any; if (backgroundImage.startsWith("data") && node_data.width && node_data.height) { const img = new Image(); img.onload = function() { const c = $doc.createElement("canvas"); c.width = node_element.clientWidth; c.height = node_element.clientHeight; const img = this as CanvasImageSource; if (c.getContext) { const ctx = c.getContext("2d") as CanvasRenderingContext2D; ctx.drawImage(img, 2, 2, node_element.clientWidth, node_element.clientHeight); const scaledImageData = c.toDataURL(); node_element.style.backgroundImage = "url(" + scaledImageData + ")"; } }; img.src = backgroundImage; } else { node_element.style.backgroundImage = "url(" + backgroundImage + ")"; } node_element.style.backgroundSize = "99%"; if ("background-rotation" in node_data) { node_element.style.transform = "rotate(" + node_data["background-rotation"] + "deg)"; } } }; clear_node_custom_style = (node: TSM_node) => { const node_element = node.view_data.element; if (node_element) { node_element.style.backgroundColor = ""; node_element.style.color = ""; } }; clear_lines = (canvas_ctx: CanvasRenderingContext2D = this.canvas_ctx as CanvasRenderingContext2D) => { canvas.clear(canvas_ctx, 0, 0, this.size.w, this.size.h); }; show_lines = (canvas_ctx: CanvasRenderingContext2D = this.canvas_ctx as CanvasRenderingContext2D) => { this.clear_lines(canvas_ctx); const nodes = this.tsm.mind!.nodes; let node = null; let pin = null; let pout = null; const _offset = this.get_view_offset(); for (const nodeid in nodes) { if (!nodeid) continue; node = nodes[nodeid]; if (!!node.isroot) { continue; } if ("visible" in node.layout_data && !node.layout_data.visible) { continue; } pin = this.layout.get_node_point_in(node); pout = this.layout.get_node_point_out(node.parent); this.draw_line(pout, pin, _offset, canvas_ctx); } }; draw_line = ( pin: ITSMPotions, pout: ITSMPotions, offset: ITSMPotions, canvas_ctx: CanvasRenderingContext2D = this.canvas_ctx as CanvasRenderingContext2D ) => { canvas_ctx.strokeStyle = this.opts.line_color; canvas_ctx.lineWidth = this.opts.line_width; canvas_ctx.lineCap = "round"; canvas.bezierto(canvas_ctx, pin.x + offset.x, pin.y + offset.y, pout.x + offset.x, pout.y + offset.y); }; }