Improve some tags + add features to codepad

- Improve Resizer + TabContainer tag
- Add bottom bar to the CodePad editor
This commit is contained in:
lxsang 2020-12-16 00:57:29 +01:00
parent 9d5a0b404c
commit 6c935e88ee
8 changed files with 422 additions and 79 deletions

View File

@ -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");
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;
}

View File

@ -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<any>} item tab descriptor
* @param {boolean} insert insert the tab content to the container ?
* @memberof TabContainerTag
*/
public addTab(item: GenericObject<any>, 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);
});
});

View File

@ -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) => {
@ -218,7 +222,7 @@ namespace OS {
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, []);
});
}

View File

@ -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<any> {
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

View File

@ -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) => {
@ -197,7 +201,7 @@ namespace OS {
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,8 +410,7 @@ namespace OS {
*/
private installMeta(meta: GenericObject<any>): Promise<any> {
return new Promise(async (resolve, reject) => {
const file = `${
this.app.meta().path
const file = `${this.app.meta().path
}/extensions.json`.asFileHandle();
try {
const data = await file.read("json");

View File

@ -10,6 +10,14 @@
<afx-vbox>
<afx-tab-bar closable="true" data-height="26" data-id = "tabbar"></afx-tab-bar>
<div data-id="datarea"></div>
<afx-resizer data-height = "3" dir = "ve" attachnext = "true" ></afx-resizer>
<afx-tab-container data-id = "bottombar" data-height="150" min-height="150" tabbarheight= "22">
<afx-hbox tabname="__(Output)" iconclass = "fa fa-file-text" class = "bottom-tab-content">
<afx-button text = "" data-id="logger-clear" iconclass="fa fa-trash" data-width="21"></afx-button>
<div data-id="output-tab" iconclass = "fa fa-file-text" >
</div>
</afx-hbox>
</afx-tab-container>
</afx-vbox>
</afx-hbox>
<div data-height="20" data-id="statctn">

View File

@ -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;
}

View File

@ -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) {
try {
const d = await dir
.read();
if (d.error) {
return reject(d.error);
}
return resolve(d.result);
})
.catch((e) => reject(__e(e)));
} catch (e) {
return reject(__e(e));
}
});
return this.setup();
}
@ -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<GUI.tag.MenuEventData>,
r: CodePadFileHandle
) => {
switch (e.data.item.data.dataid) {
case "cmdpalette":
return this.spotlight.run(this);
case "bottombar":
return this.toggleBottomBar();
default:
break;
}
r
},
},
];
@ -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 = $("<pre></pre>")
.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",