diff --git a/src/core/tags/ResizerTag.ts b/src/core/tags/ResizerTag.ts index cc4975d..685ae91 100644 --- a/src/core/tags/ResizerTag.ts +++ b/src/core/tags/ResizerTag.ts @@ -87,7 +87,7 @@ namespace OS { * @param {*} [d] * @memberof ResizerTag */ - protected reload(d?: any): void {} + protected reload(d?: any): void { } /** * Setter: * @@ -133,6 +133,23 @@ namespace OS { return $(this).attr("dir"); } + + /** + * Getter : Check whether the resizer should attach to its next or previous element + * + * Setter: if `v=true` select next element as attached element of the resizer, otherwise + * select the previous element + * @readonly + * @type {boolean} + * @memberof ResizerTag + */ + get attachnext(): boolean { + return this.hasattr("attachnext"); + } + set attachnext(v: boolean) { + this.attsw(v, "attachnext"); + } + /** * Setter: * - set the resize event callback @@ -158,10 +175,19 @@ namespace OS { protected mount(): void { $(this).css(" display", "block"); const tagname = $(this._parent).prop("tagName"); - this._resizable_el = - $(this).prev().length === 1 - ? $(this).prev()[0] - : undefined; + if (this.attachnext) { + this._resizable_el = + $(this).next().length === 1 + ? $(this).next()[0] + : undefined; + } + else { + this._resizable_el = + $(this).prev().length === 1 + ? $(this).prev()[0] + : undefined; + } + if (tagname === "AFX-HBOX") { this.dir = "hz"; } else if (tagname === "AFX-VBOX") { @@ -217,7 +243,13 @@ namespace OS { return; } const offset = $(this._resizable_el).offset(); - let w = Math.round(e.clientX - offset.left); + let w = 0; + if (this.attachnext) { + w = Math.round(offset.left + $(this._resizable_el).width() - e.clientX); + } + else { + w = Math.round(e.clientX - offset.left); + } if (w < this._minsize) { w = this._minsize; } @@ -245,7 +277,13 @@ namespace OS { return; } const offset = $(this._resizable_el).offset(); - let h = Math.round(e.clientY - offset.top); + let h = 0; + if (this.attachnext) { + h = Math.round(offset.top + $(this._resizable_el).height() - e.clientY); + } + else { + h = Math.round(e.clientY - offset.top); + } if (h < this._minsize) { h = this._minsize; } diff --git a/src/core/tags/TabContainerTag.ts b/src/core/tags/TabContainerTag.ts index efd05ab..5abc707 100644 --- a/src/core/tags/TabContainerTag.ts +++ b/src/core/tags/TabContainerTag.ts @@ -57,7 +57,7 @@ namespace OS { */ constructor() { super(); - this._ontabselect = (e) => {}; + this._ontabselect = (e) => { }; } /** @@ -77,7 +77,7 @@ namespace OS { * @param {*} [d] * @memberof TabContainerTag */ - protected reload(d?: any): void {} + protected reload(d?: any): void { } /** * Set the tab select event handle @@ -166,6 +166,50 @@ namespace OS { (this.refs.wrapper as TileLayoutTag).calibrate(); } + /** + * Add a new tab with container to the container + * + * item should be in the following format: + * + * ```ts + * { + * text: string, + * icon?: string, + * iconclass?: string, + * container: HTMLElement + * } + * ``` + * + * @param {GenericObject} item tab descriptor + * @param {boolean} insert insert the tab content to the container ? + * @memberof TabContainerTag + */ + public addTab(item: GenericObject, insert: boolean): void { + if (insert) { + $(this.refs.yield).append(item.container); + } + $(item.container) + .css("width", "100%") + .css("height", "100%") + .hide(); + const el = (this.refs.bar as TabBarTag).push( + item + ); + el.selected = true; + } + + /** + * Remove a tab from the container + * + * @param {ListViewItemTag} tab the tab item to be removed + * @memberof TabContainerTag + */ + public removeTab(tab: ListViewItemTag): void { + if (tab.data.container) { + $(tab.data.container).remove(); + } + (this.refs.bar as TabBarTag).delete(tab); + } /** * Mount the tag and bind basic events * @@ -194,14 +238,7 @@ namespace OS { item.iconclass = $(e).attr("iconclass"); } item.container = e; - $(e) - .css("width", "100%") - .css("height", "100%") - .hide(); - const el = (this.refs.bar as TabBarTag).push( - item - ); - el.selected = true; + this.addTab(item, false); }); }); diff --git a/src/packages/CodePad/AntOSDK.ts b/src/packages/CodePad/AntOSDK.ts index f37ed1e..c06ffd8 100644 --- a/src/packages/CodePad/AntOSDK.ts +++ b/src/packages/CodePad/AntOSDK.ts @@ -37,6 +37,7 @@ namespace OS { mimes: ["dir"], }) .then((d) => { + this.logger().clear(); return this.mktpl(d.file.path, d.name, true); }); } @@ -48,16 +49,17 @@ namespace OS { * @memberof AntOSDK */ init(): void { + this.logger().clear(); const dir = this.app.currdir; if (!dir || !dir.basename) { return this.create(); } dir.read().then((d) => { if (d.error) { - return this.notify(__("Cannot read folder: {0}", dir.path)); + return this.logger().error(__("Cannot read folder: {0}", dir.path)); } if (d.result.length !== 0) { - return this.notify( + return this.logger().error( __("The folder is not empty: {0}", dir.path) ); } @@ -71,6 +73,7 @@ namespace OS { * @memberof AntOSDK */ buildnrun(): void { + this.logger().clear(); this.metadata("project.json") .then(async (meta) => { try { @@ -78,13 +81,13 @@ namespace OS { try { return this.run(meta); } catch (e) { - return this.error(__("Unable to run project"), e); + return this.logger().error(__("Unable to run project: {0}", e.stack)); } } catch (e_1) { - return this.error(__("Unable to build project"), e_1); + return this.logger().error(__("Unable to build project: {0}", e_1.stack)); } }) - .catch((e) => this.error(__("Unable to read meta-data"), e)); + .catch((e) => this.logger().error(__("Unable to read meta-data: {0}", e.stack))); } /** @@ -93,6 +96,7 @@ namespace OS { * @memberof AntOSDK */ release(): void { + this.logger().clear(); this.metadata("project.json") .then(async (meta) => { try { @@ -103,16 +107,16 @@ namespace OS { `${meta.root}/build/release/${meta.name}.zip` ); } catch (e) { - return this.error( - __("Unable to create package archive"), - e + return this.logger().error( + __("Unable to create package archive: {}", + e.stack) ); } } catch (e_1) { - return this.error(__("Unable to build project"), e_1); + return this.logger().error(__("Unable to build project: {0}", e_1.stack)); } }) - .catch((e) => this.error(__("Unable to read meta-data"), e)); + .catch((e) => this.logger().error(__("Unable to read meta-data: {0}", e.stack))); } // private functions @@ -153,14 +157,14 @@ namespace OS { `${rpath}/README.md`.asFileHandle() as application.CodePadFileHandle ); } catch (e) { - return this.error( - __("Unable to create template files"), - e + return this.logger().error( + __("Unable to create template files: {0}", + e.stack) ); } }) .catch((e) => - this.error(__("Unable to create project directory"), e) + this.logger().error(__("Unable to create project directory: {0}", e.stack)) ); } @@ -178,7 +182,7 @@ namespace OS { return resolve(); } const file = list.splice(0, 1)[0].asFileHandle(); - this.notify(__("Verifying: {0}", file.path)); + this.logger().info(__("Verifying: {0}", file.path)); return file .read() .then((data) => { @@ -214,11 +218,11 @@ namespace OS { (v: string) => `${meta.root}/${v}` ); try { - await this.verify(list.map((x: string) =>x)); + await this.verify(list.map((x: string) => x)); try { const code = await this.cat(list, ""); const jsrc = CoffeeScript.compile(code); - this.notify(__("Compiled successful")); + this.logger().info(__("Compiled successful")); return resolve(jsrc); } catch (e) { return reject(__e(e)); @@ -282,7 +286,7 @@ namespace OS { options ); if (result_1.error) { - this.notify( + this.logger().error( __( "Unable to minify code: {0}", result_1.error @@ -372,9 +376,9 @@ namespace OS { if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; } - this.notify(__("Installing...")); + this.logger().info(__("Installing...")); setting.system.packages[meta.name] = v; - this.notify(__("Running {0}...", meta.name)); + this.logger().info(__("Running {0}...", meta.name)); return GUI.forceLaunch(meta.name, []); }); } diff --git a/src/packages/CodePad/BaseExtension.ts b/src/packages/CodePad/BaseExtension.ts index c952e52..c10c953 100644 --- a/src/packages/CodePad/BaseExtension.ts +++ b/src/packages/CodePad/BaseExtension.ts @@ -74,6 +74,22 @@ namespace OS { return this.app.error(m, e); } + + /** + * + * + * @protected + * @return {Logger} editor logger + * @memberof BaseExtension + */ + protected logger() { + if(!this.app.setting.showBottomBar) + { + this.app.showBottomBar(true); + } + return this.app.logger; + } + /** * * @@ -272,7 +288,7 @@ namespace OS { * @memberof BaseExtension */ protected mkar(src: string, dest: string): Promise { - this.notify(__("Preparing for release")); + this.logger().info(__("Preparing for release")); return new Promise((resolve, reject) => { return new Promise(async (r, e) => { try { @@ -314,7 +330,7 @@ namespace OS { .write("base64") .then((r: any) => { resolve(); - return this.notify( + return this.logger().info( __( "Archive is generated at: {0}", dest diff --git a/src/packages/CodePad/ExtensionMaker.ts b/src/packages/CodePad/ExtensionMaker.ts index 8c6ec03..3e079e7 100644 --- a/src/packages/CodePad/ExtensionMaker.ts +++ b/src/packages/CodePad/ExtensionMaker.ts @@ -22,6 +22,7 @@ namespace OS { * @memberof ExtensionMaker */ create(): void { + this.logger().clear(); this.app .openDialog("FileDialog", { title: "__(New CodePad extension at)", @@ -39,6 +40,7 @@ namespace OS { * @memberof ExtensionMaker */ buildnrun(): void { + this.logger().clear(); this.metadata("extension.json") .then(async (meta) => { try { @@ -46,13 +48,13 @@ namespace OS { try { return this.run(meta); } catch (e) { - return this.error(__("Unable to run extension"), e); + return this.logger().error(__("Unable to run extension: {0}", e.stack)); } } catch (e_1) { - return this.error(__("Unable to build extension"), e_1); + return this.logger().error(__("Unable to build extension: {0}", e_1.stack)); } }) - .catch((e) => this.error(__("Unable to read meta-data"), e)); + .catch((e) => this.logger().error(__("Unable to read meta-data:{0}", e.stack))); } /** @@ -61,6 +63,7 @@ namespace OS { * @memberof ExtensionMaker */ release(): void { + this.logger().clear(); this.metadata("extension.json") .then(async (meta) => { try { @@ -71,16 +74,16 @@ namespace OS { `${meta.root}/build/release/${meta.meta.name}.zip` ); } catch (e) { - return this.error( - __("Unable to create archive"), - e - ); + return this.logger().error( + __("Unable to create archive: {0}", + e.stack + )); } } catch (e_1) { - return this.error(__("Unable to build extension"), e_1); + return this.logger().error(__("Unable to build extension: {0}", e_1.stack)); } }) - .catch((e) => this.error(__("Unable to read meta-data"), e)); + .catch((e) => this.logger().error(__("Unable to read meta-data: {0}", e.stack))); } /** @@ -89,6 +92,7 @@ namespace OS { * @memberof ExtensionMaker */ install(): void { + this.logger().clear(); this.app .openDialog("FileDialog", { title: "__(Select extension archive)", @@ -97,10 +101,10 @@ namespace OS { .then(async (d) => { try { await this.installZip(d.file.path); - this.notify(__("Extension installed")); + this.logger().info(__("Extension installed")); return this.app.loadExtensionMetaData(); } catch (e) { - return this.error(__("Unable to install extension"), e); + return this.logger().error(__("Unable to install extension: {0}", e.stack)); } }); } @@ -135,14 +139,14 @@ namespace OS { `${rpath}/${name}.coffee`.asFileHandle() as application.CodePadFileHandle ); } catch (e) { - return this.error( - __("Unable to create extension template"), - e + return this.logger().error( + __("Unable to create extension template: {0}", + e.stack) ); } }) .catch((e) => - this.error(__("Unable to create extension directories"), e) + this.logger().error(__("Unable to create extension directories: {0}", e.stack)) ); } @@ -160,7 +164,7 @@ namespace OS { return resolve(); } const file = list.splice(0, 1)[0].asFileHandle(); - this.notify(__("Verifying: {0}", file.path)); + this.logger().info(__("Verifying: {0}", file.path)); return file .read() .then((data) => { @@ -193,11 +197,11 @@ namespace OS { (v) => `${meta.root}/${v}` ); try { - await this.verify(list.map((x: string) =>x)); + await this.verify(list.map((x: string) => x)); try { const code = await this.cat(list, ""); const jsrc = CoffeeScript.compile(code); - this.notify(__("Compiled successful")); + this.logger().info(__("Compiled successful")); return resolve(jsrc); } catch (e) { return reject(__e(e)); @@ -406,9 +410,8 @@ namespace OS { */ private installMeta(meta: GenericObject): Promise { return new Promise(async (resolve, reject) => { - const file = `${ - this.app.meta().path - }/extensions.json`.asFileHandle(); + const file = `${this.app.meta().path + }/extensions.json`.asFileHandle(); try { const data = await file.read("json"); const names = []; diff --git a/src/packages/CodePad/assets/scheme.html b/src/packages/CodePad/assets/scheme.html index fd60171..fe5cf66 100644 --- a/src/packages/CodePad/assets/scheme.html +++ b/src/packages/CodePad/assets/scheme.html @@ -10,6 +10,14 @@
+ + + + +
+
+
+
diff --git a/src/packages/CodePad/css/main.css b/src/packages/CodePad/css/main.css index 9ce237a..a774baf 100644 --- a/src/packages/CodePad/css/main.css +++ b/src/packages/CodePad/css/main.css @@ -1,3 +1,6 @@ +afx-app-window[data-id = "codepad"] .ace_editor { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace; +} afx-app-window[data-id = "codepad"] afx-tab-bar> afx-list-view > div.list-container { /*border-top: 1px solid #272822;*/ @@ -34,8 +37,14 @@ afx-app-window[data-id = "codepad"] div.afx-window-content { } afx-app-window[data-id = "codepad"] afx-resizer { background-color:#272822; - border-right: 1px solid #37373d; + border-right: 1px solid #656565; + border-bottom: 1px solid #656565; } + +afx-app-window[data-id = "codepad"] .bottom-tab-content { + background-color:#272822; +} + afx-app-window[data-id = "codepad"] .afx-window-wrapper afx-tree-view{ color: white; padding: 0; @@ -106,3 +115,31 @@ afx-app-window[data-id = "cmd-win"] input{ afx-app-window[data-id = "cmd-win"] .afx-window-content{ background-color:#272822; } + +afx-app-window[data-id = "codepad"] div[data-id="output-tab"] { + overflow-y: auto; + overflow-x: hidden; +} + +afx-app-window[data-id = "codepad"] div[data-id="output-tab"] pre { + margin: 3px; + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + font-family: monospace; +} + +afx-app-window[data-id = "codepad"] div[data-id="output-tab"] pre.code-pad-log-error { + color: red; +} + +afx-app-window[data-id = "codepad"] div[data-id="output-tab"] pre.code-pad-log-warn { + color: orange; +} + +afx-app-window[data-id = "codepad"] afx-button[ data-id="logger-clear" ] button{ + border: 0; + background: transparent; +} \ No newline at end of file diff --git a/src/packages/CodePad/main.ts b/src/packages/CodePad/main.ts index 8201efb..9e433dd 100644 --- a/src/packages/CodePad/main.ts +++ b/src/packages/CodePad/main.ts @@ -113,6 +113,14 @@ namespace OS { */ private sidebar: GUI.tag.VBoxTag; + /** + * Reference to the bottom bar + * + * @private + * @type {GUI.tag.TabContainerTag} + * @memberof CodePad + */ + private bottombar: GUI.tag.TabContainerTag; /** * Reference to the editor tab bar UI * @@ -175,6 +183,15 @@ namespace OS { */ spotlight: CMDMenu; + + /** + * Reference to the editor logger + * + * @type {Logger} + * @memberof CodePad + */ + logger: Logger; + /** * Extension prototype definition will be stored * in this class variable @@ -194,6 +211,14 @@ namespace OS { */ static CMDMenu: typeof CMDMenu; + /** + * Prototype definition of a Logger + * + * @static + * @type {typeof Logger} + * @memberof CodePad + */ + static Logger: typeof Logger; /** * Prototype definition of CodePad CommandPalette * @@ -240,27 +265,29 @@ namespace OS { this.extensions = {}; this.fileview = this.find("fileview") as GUI.tag.FileViewTag; this.sidebar = this.find("sidebar") as GUI.tag.VBoxTag; + this.bottombar = this.find("bottombar") as GUI.tag.TabContainerTag; this.tabbar = this.find("tabbar") as GUI.tag.TabBarTag; this.langstat = this.find("langstat") as GUI.tag.LabelTag; this.editorstat = this.find("editorstat") as GUI.tag.LabelTag; - + this.logger = new Logger(this.find("output-tab")); this.fileview.fetch = (path) => - new Promise(function (resolve, reject) { + new Promise(async function (resolve, reject) { let dir: API.VFS.BaseFileHandle; if (typeof path === "string") { dir = path.asFileHandle(); } else { dir = path; } - return dir - .read() - .then(function (d) { - if (d.error) { - return reject(d.error); - } - return resolve(d.result); - }) - .catch((e) => reject(__e(e))); + try { + const d = await dir + .read(); + if (d.error) { + return reject(d.error); + } + return resolve(d.result); + } catch (e) { + return reject(__e(e)); + } }); return this.setup(); } @@ -297,7 +324,7 @@ namespace OS { pos: any, prefix: any, callback: any - ) {}, + ) { }, }); this.editor.getSession().setUseWrapMode(true); this.editormux = false; @@ -416,13 +443,13 @@ namespace OS { e.data.to.update(p1); (e.data .from as GUI.tag.TreeViewTag).parent.update( - p2 - ); + p2 + ); } else { (e.data .from as GUI.tag.TreeViewTag).parent.update( - p2 - ); + p2 + ); e.data.to.update(p1); } }) @@ -439,9 +466,19 @@ namespace OS { return this.fileview.update(path); }); + (this.find("logger-clear") as GUI.tag.ButtonTag).onbtclick = () => + { + this.logger.clear() + } + + if (this.setting.showBottomBar === undefined) { + this.setting.showBottomBar = false; + } + this.loadExtensionMetaData(); this.initCommandPalete(); this.toggleSideBar(); + this.applyAllSetting(); return this.openFile(this.currfile); } @@ -636,6 +673,47 @@ namespace OS { this.trigger("resize"); } + + /** + * Apply [[showBottomBar]] from user setting value + * + * @protected + * @param {string} k + * @memberof CodePad + */ + protected applySetting(k: string): void { + if (k == "showBottomBar") { + this.showBottomBar(this.setting.showBottomBar); + } + } + + /** + * Show or hide the bottom bar and + * save the value to user setting + * + * @param {boolean} v + * @memberof CodePad + */ + public showBottomBar(v: boolean): void { + this.setting.showBottomBar = v; + if (v) { + $(this.bottombar).show(); + } + else { + $(this.bottombar).hide(); + } + this.trigger("resize"); + } + + /** + * toggle the bottom bar + * + * @memberof CodePad + */ + private toggleBottomBar(): void { + this.showBottomBar(!this.setting.showBottomBar); + } + /** * Add an action to the [[CommandPalette]]'s spotlight * @@ -1142,12 +1220,26 @@ namespace OS { dataid: "cmdpalette", shortcut: "A-P", }, + { + text: "__(Toggle bottom bar)", + dataid: "bottombar" + } ], onchildselect: ( e: GUI.TagEventType, r: CodePadFileHandle ) => { - return this.spotlight.run(this); + switch (e.data.item.data.dataid) { + case "cmdpalette": + return this.spotlight.run(this); + + case "bottombar": + return this.toggleBottomBar(); + + default: + break; + } + r }, }, ]; @@ -1181,7 +1273,7 @@ namespace OS { this.shortcut = shortcut; this.nodes = []; this.parent = undefined; - this.select = function (e) {}; + this.select = function (e) { }; } /** @@ -1255,7 +1347,115 @@ namespace OS { return m; }; + + /** + * This class handles log output to the Editor output container + * + * @class Logger + */ + class Logger { + + /** + * Referent to the log container + * + * @private + * @type {HTMLElement} + * @memberof Logger + */ + private target: HTMLElement; + + + /** + * Creates an instance of Logger. + * @param {HTMLElement} el target container + * @memberof Logger + */ + constructor(el: HTMLElement) { + this.target = el; + } + + /** + * Log level info + * + * @param {string|FormattedString} s + * @memberof Logger + */ + info(s: string | FormattedString): void { + this.log("info", s, true); + } + + /** + * Log level warning + * + * @param {string|FormattedString} s + * @memberof Logger + */ + warn(s: string | FormattedString): void { + this.log("warn", s, true); + } + + /** + * Log level error + * + * @param {string|FormattedString} s + * @memberof Logger + */ + error(s: string|FormattedString): void { + this.log("error", s, true); + } + + + /** + * Log a string to target container + * + * @private + * @param {string} c class name of the appended log element + * @param {string|FormattedString} s log string + * @param {boolean} showtime define whether the logger should insert datetime prefix + * in the log string + * @memberof Logger + */ + private log(c: string, s: string|FormattedString, showtime: boolean): void { + let el = $("
")
+                    .attr("class", `code-pad-log-${c}`);
+                if (showtime) {
+                    let date = new Date();
+                    let prefix = date.getDate() + "/"
+                        + (date.getMonth() + 1) + "/"
+                        + date.getFullYear() + " "
+                        + date.getHours() + ":"
+                        + date.getMinutes() + ":"
+                        + date.getSeconds();
+                    el.text(`[${prefix}]: ${s.__()}`);
+                }
+                else {
+                    el.text(s.__());
+                }
+                $(this.target).append(el);
+            }
+            
+            /**
+             * Print a log message without prefix
+             *
+             * @param {string|FormattedString} s text to print
+             * @memberof Logger
+             */
+            print(s: string|FormattedString): void {
+                this.log("info", s, false);
+            }
+
+            /**
+             * Empty the log container
+             *
+             * @memberof Logger
+             */
+            clear(): void {
+                $(this.target).empty();
+            }
+        }
+
         CodePad.CMDMenu = CMDMenu;
+        CodePad.Logger = Logger;
 
         CodePad.dependencies = [
             "os://scripts/ace/ace.js",