mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-07-27 03:09:45 +02:00
switch to es6 from coffeescript
This commit is contained in:
195
src/packages/CodePad/BaseExtension.js
Normal file
195
src/packages/CodePad/BaseExtension.js
Normal file
@ -0,0 +1,195 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
CodePad.BaseExtension = class BaseExtension {
|
||||
|
||||
constructor(app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
preload() {
|
||||
return Ant.OS.API.require(this.dependencies());
|
||||
}
|
||||
|
||||
import(libs) {
|
||||
return Ant.OS.API.require(libs);
|
||||
}
|
||||
|
||||
basedir() {
|
||||
return `${this.app.meta().path}/extensions`;
|
||||
}
|
||||
|
||||
notify(m) {
|
||||
return this.app.notify(m);
|
||||
}
|
||||
|
||||
error(m, e) {
|
||||
return this.app.error(m, e);
|
||||
}
|
||||
|
||||
dependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
cat(list, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(data); }
|
||||
const file = (list.splice(0, 1))[0].asFileHandle();
|
||||
return file
|
||||
.read()
|
||||
.then(text => {
|
||||
data = data + "\n" + text;
|
||||
return this.cat(list, data)
|
||||
.then(d => resolve(d))
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
copy(files, to) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (files.length === 0) { return resolve(); }
|
||||
const file = (files.splice(0, 1))[0].asFileHandle();
|
||||
const tof = `${to}/${file.basename}`.asFileHandle();
|
||||
return file.onready().then(meta => {
|
||||
if (meta.type === "dir") {
|
||||
// copy directory
|
||||
const desdir = to.asFileHandle();
|
||||
return desdir.mk(file.basename).then(() => {
|
||||
// read the dir content
|
||||
return file.read().then(data => {
|
||||
const list = (Array.from(data.result).map((v) => v.path));
|
||||
return this.copy(list, `${desdir.path}/${file.basename}`)
|
||||
.then(() => {
|
||||
return this.copy(files, to)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
} else {
|
||||
// copy file
|
||||
return file.read("binary")
|
||||
.then(data => {
|
||||
return tof.setCache(new Blob([data], { type: file.info.mime }))
|
||||
.write(file.info.mime)
|
||||
.then(d => {
|
||||
return this.copy(files, to)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
});
|
||||
}).catch(e => reject(__e(e)));
|
||||
}
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
aradd(list, zip, base) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(zip); }
|
||||
const path = (list.splice(0, 1))[0];
|
||||
const file = path.asFileHandle();
|
||||
return file.onready().then(meta => {
|
||||
if (meta.type === "dir") {
|
||||
return file.read().then(d => {
|
||||
const l = (Array.from(d.result).map((v) => v.path));
|
||||
return this.aradd(l, zip, `${base}${file.basename}/`)
|
||||
.then(() => {
|
||||
return this.aradd(list, zip, base)
|
||||
.then(() => resolve(zip))
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
} else {
|
||||
return file.read("binary").then(d => {
|
||||
const zpath = `${base}${file.basename}`.replace(/^\/+|\/+$/g, '');
|
||||
zip.file(zpath, d, { binary: true });
|
||||
return this.aradd(list, zip, base)
|
||||
.then(() => resolve(zip))
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
mkar(src, dest) {
|
||||
this.notify(__("Preparing for release"));
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((r, e) => {
|
||||
return this.import(["os://scripts/jszip.min.js"]).then(() => src.asFileHandle()
|
||||
.read().then(d => r(d.result)).catch(ex => e(__e(ex)))).catch(ex => e(__e(ex)));
|
||||
}).then(files => {
|
||||
return new Promise((r, e) => {
|
||||
const zip = new JSZip();
|
||||
return this.aradd((Array.from(files).map((v) => v.path)), zip, "/")
|
||||
.then(z => r(z))
|
||||
.catch(ex => e(__e(ex)));
|
||||
});
|
||||
}).then(zip => {
|
||||
return zip.generateAsync({ type: "base64" }).then(data => {
|
||||
return dest.asFileHandle()
|
||||
.setCache('data:application/zip;base64,' + data)
|
||||
.write("base64").then(r => {
|
||||
return this.notify(__("Archive is generated at: {0}", dest));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
mkdirAll(list) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(); }
|
||||
const path = (list.splice(0, 1))[0].asFileHandle();
|
||||
return path.parent().mk(path.basename)
|
||||
.then(d => {
|
||||
this.app.trigger("filechange", { file: path.parent(), type: "dir" });
|
||||
return this.mkdirAll(list)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
mkfileAll(list, path, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(); }
|
||||
const item = (list.splice(0, 1))[0];
|
||||
return `${this.basedir()}/${item[0]}`
|
||||
.asFileHandle()
|
||||
.read()
|
||||
.then(data => {
|
||||
const file = item[1].asFileHandle();
|
||||
return file
|
||||
.setCache(data.format(name, `${path}/${name}`))
|
||||
.write("text/plain")
|
||||
.then(() => {
|
||||
this.app.trigger("filechange", { file, type: "file" });
|
||||
return this.mkfileAll(list, path, name)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
metadata(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.app.currdir) {
|
||||
return reject(this.app._api.throwe(__("Current folder is not found")));
|
||||
}
|
||||
return `${this.app.currdir.path}/${file}`
|
||||
.asFileHandle()
|
||||
.read("json")
|
||||
.then(data => resolve(data)).catch(e => {
|
||||
return reject(this.app._api.throwe(__("Unable to read meta-data")));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
CodePad.extensions = {};
|
98
src/packages/CodePad/CommandPalette.js
Normal file
98
src/packages/CodePad/CommandPalette.js
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
class CommandPalette extends this.OS.GUI.BasicDialog {
|
||||
constructor() {
|
||||
super("CommandPalete", CommandPalette.scheme);
|
||||
}
|
||||
|
||||
main() {
|
||||
super.main();
|
||||
const offset = $(".afx-window-content", this.parent.scheme).offset();
|
||||
const pw = this.parent.scheme.get("width") / 5;
|
||||
this.scheme.set("width", 3 * pw);
|
||||
$(this.scheme).offset({ top: offset.top - 2, left: offset.left + pw });
|
||||
var cb = e => {
|
||||
if (($(e.target)).closest(this.scheme).length > 0) {
|
||||
return $(this.find("searchbox")).focus();
|
||||
} else {
|
||||
$(document).unbind("mousedown", cb);
|
||||
return this.quit();
|
||||
}
|
||||
};
|
||||
$(document).on("mousedown", cb);
|
||||
$(this.find("searchbox")).focus();
|
||||
this.cmdlist = this.find("container");
|
||||
if (this.data) { this.cmdlist.set("data", (Array.from(this.data.child))); }
|
||||
$(this.cmdlist).click(e => {
|
||||
return this.selectCommand();
|
||||
});
|
||||
|
||||
this.searchbox = this.find("searchbox");
|
||||
return ($(this.searchbox)).keyup(e => {
|
||||
return this.search(e);
|
||||
});
|
||||
}
|
||||
|
||||
search(e) {
|
||||
let v;
|
||||
switch (e.which) {
|
||||
case 27:
|
||||
// escape key
|
||||
this.quit();
|
||||
if (this.data.parent && this.data.parent.run) { return this.data.parent.run(this.parent); }
|
||||
break;
|
||||
case 37:
|
||||
return e.preventDefault();
|
||||
case 38:
|
||||
this.cmdlist.selectPrev();
|
||||
return e.preventDefault();
|
||||
case 39:
|
||||
return e.preventDefault();
|
||||
case 40:
|
||||
this.cmdlist.selectNext();
|
||||
return e.preventDefault();
|
||||
case 13:
|
||||
e.preventDefault();
|
||||
return this.selectCommand();
|
||||
default:
|
||||
var text = this.searchbox.value;
|
||||
if (text.length === 2) { this.cmdlist.set("data", ((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(this.data.child)) { result1.push(v);
|
||||
}
|
||||
return result1;
|
||||
})())); }
|
||||
if (text.length < 3) { return; }
|
||||
var result = [];
|
||||
var term = new RegExp(text, 'i');
|
||||
for (v of Array.from(this.data.child)) { if (v.text.match(term)) { result.push(v); } }
|
||||
return this.cmdlist.set("data", result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
selectCommand() {
|
||||
const el = this.cmdlist.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
el.set("selected", false);
|
||||
let result = false;
|
||||
if (this.handle) { result = this.handle({ data: { item: el } }); }
|
||||
if (!result) { return this.quit(); }
|
||||
}
|
||||
}
|
||||
|
||||
CommandPalette.scheme = `\
|
||||
<afx-app-window data-id = "cmd-win"
|
||||
apptitle="" minimizable="false"
|
||||
resizable = "false" width="200" height="200">
|
||||
<afx-vbox>
|
||||
<input data-height="25" type = "text" data-id="searchbox"/>
|
||||
<afx-list-view data-id="container"></afx-list-view>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
@ -1,13 +1,10 @@
|
||||
coffee_files = coffees/CommandPalette.coffee coffees/main.coffee coffees/BaseExtension.coffee
|
||||
module_files = CommandPalette.js main.js BaseExtension.js
|
||||
|
||||
module_dir = extensions
|
||||
module_dir_src = coffees/extensions
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = css/main.css
|
||||
|
||||
copyfiles = assets/scheme.html package.json extensions.json
|
||||
copyfiles = assets/scheme.html package.json extensions.json extensions
|
||||
|
||||
|
||||
PKG_NAME=CodePad
|
||||
|
@ -1,168 +0,0 @@
|
||||
class CodePad.BaseExtension
|
||||
|
||||
constructor: (@app) ->
|
||||
|
||||
preload: () ->
|
||||
Ant.OS.API.require @dependencies()
|
||||
|
||||
import: (libs) ->
|
||||
Ant.OS.API.require libs
|
||||
|
||||
basedir: () ->
|
||||
"#{@app.meta().path}/extensions"
|
||||
|
||||
notify: (m) ->
|
||||
@app.notify m
|
||||
|
||||
error: (m, e) ->
|
||||
@app.error m, e
|
||||
|
||||
dependencies: () ->
|
||||
[]
|
||||
|
||||
cat: (list, data) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve data if list.length is 0
|
||||
file = (list.splice 0, 1)[0].asFileHandle()
|
||||
file
|
||||
.read()
|
||||
.then (text) =>
|
||||
data = data + "\n" + text
|
||||
@cat list, data
|
||||
.then (d) -> resolve d
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
copy: (files, to) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if files.length is 0
|
||||
file = (files.splice 0, 1)[0].asFileHandle()
|
||||
tof = "#{to}/#{file.basename}".asFileHandle()
|
||||
file.onready().then (meta) =>
|
||||
if meta.type is "dir"
|
||||
# copy directory
|
||||
desdir = to.asFileHandle()
|
||||
desdir.mk(file.basename).then () =>
|
||||
# read the dir content
|
||||
file.read().then (data) =>
|
||||
list = (v.path for v in data.result)
|
||||
@copy list, "#{desdir.path}/#{file.basename}"
|
||||
.then () =>
|
||||
@copy files, to
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) ->
|
||||
reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) ->
|
||||
reject __e e
|
||||
else
|
||||
# copy file
|
||||
file.read("binary")
|
||||
.then (data) =>
|
||||
tof.setCache(new Blob [data], { type: file.info.mime })
|
||||
.write(file.info.mime)
|
||||
.then (d) =>
|
||||
@copy files, to
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) ->
|
||||
reject __e e
|
||||
|
||||
aradd: (list, zip, base) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve(zip) if list.length is 0
|
||||
path = (list.splice 0, 1)[0]
|
||||
file = path.asFileHandle()
|
||||
file.onready().then (meta) =>
|
||||
if meta.type is "dir"
|
||||
file.read().then (d) =>
|
||||
l = (v.path for v in d.result)
|
||||
@aradd l, zip, "#{base}#{file.basename}/"
|
||||
.then () =>
|
||||
@aradd list, zip, base
|
||||
.then () -> resolve(zip)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
else
|
||||
file.read("binary").then (d) =>
|
||||
zpath = "#{base}#{file.basename}".replace(/^\/+|\/+$/g, '')
|
||||
zip.file zpath, d, { binary: true }
|
||||
@aradd list, zip, base
|
||||
.then () -> resolve(zip)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
mkar: (src, dest) ->
|
||||
@notify __("Preparing for release")
|
||||
new Promise (resolve, reject) =>
|
||||
new Promise (r, e) =>
|
||||
@import(["os://scripts/jszip.min.js"]).then () ->
|
||||
src.asFileHandle()
|
||||
.read().then (d) ->
|
||||
r d.result
|
||||
.catch (ex) -> e __e ex
|
||||
.catch (ex) -> e __e ex
|
||||
.then (files) =>
|
||||
new Promise (r, e) =>
|
||||
zip = new JSZip()
|
||||
@aradd (v.path for v in files), zip, "/"
|
||||
.then (z) -> r(z)
|
||||
.catch (ex) -> e __e ex
|
||||
.then (zip) =>
|
||||
zip.generateAsync({ type: "base64" }).then (data) =>
|
||||
dest.asFileHandle()
|
||||
.setCache('data:application/zip;base64,' + data)
|
||||
.write("base64").then (r) =>
|
||||
@notify __("Archive is generated at: {0}", dest)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
mkdirAll: (list) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if list.length is 0
|
||||
path = (list.splice 0, 1)[0].asFileHandle()
|
||||
path.parent().mk path.basename
|
||||
.then (d) =>
|
||||
@app.trigger "filechange", { file: path.parent(), type: "dir" }
|
||||
@mkdirAll list
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
mkfileAll: (list, path, name) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if list.length is 0
|
||||
item = (list.splice 0, 1)[0]
|
||||
"#{@basedir()}/#{item[0]}"
|
||||
.asFileHandle()
|
||||
.read()
|
||||
.then (data) =>
|
||||
file = item[1].asFileHandle()
|
||||
file
|
||||
.setCache(data.format name, "#{path}/#{name}")
|
||||
.write "text/plain"
|
||||
.then () =>
|
||||
@app.trigger "filechange", { file: file, type: "file" }
|
||||
@mkfileAll list, path, name
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
metadata: (file) ->
|
||||
new Promise (resolve, reject) =>
|
||||
if not @app.currdir
|
||||
return reject @app._api.throwe __("Current folder is not found")
|
||||
"#{@app.currdir.path}/#{file}"
|
||||
.asFileHandle()
|
||||
.read("json")
|
||||
.then (data) ->
|
||||
resolve data
|
||||
.catch (e) =>
|
||||
reject @app._api.throwe __("Unable to read meta-data")
|
||||
|
||||
CodePad.extensions = {}
|
@ -1,74 +0,0 @@
|
||||
class CommandPalette extends this.OS.GUI.BasicDialog
|
||||
constructor: () ->
|
||||
super "CommandPalete", CommandPalette.scheme
|
||||
|
||||
main: () ->
|
||||
super.main()
|
||||
offset = $(".afx-window-content", @parent.scheme).offset()
|
||||
pw = @parent.scheme.get("width") / 5
|
||||
@scheme.set "width", 3 * pw
|
||||
$(@scheme).offset { top: offset.top - 2, left: offset.left + pw }
|
||||
cb = (e) =>
|
||||
if ($ e.target).closest(@scheme).length > 0
|
||||
$(@find "searchbox").focus()
|
||||
else
|
||||
$(document).unbind "mousedown", cb
|
||||
@quit()
|
||||
$(document).on "mousedown", cb
|
||||
$(@find "searchbox").focus()
|
||||
@cmdlist = @find("container")
|
||||
@cmdlist.set "data", (v for v in @data.child) if @data
|
||||
$(@cmdlist).click (e) =>
|
||||
@selectCommand()
|
||||
|
||||
@searchbox = @find "searchbox"
|
||||
($ @searchbox).keyup (e) =>
|
||||
@search e
|
||||
|
||||
search: (e) ->
|
||||
switch e.which
|
||||
when 27
|
||||
# escape key
|
||||
@quit()
|
||||
@data.parent.run(@parent) if @data.parent and @data.parent.run
|
||||
when 37
|
||||
e.preventDefault()
|
||||
when 38
|
||||
@cmdlist.selectPrev()
|
||||
e.preventDefault()
|
||||
when 39
|
||||
e.preventDefault()
|
||||
when 40
|
||||
@cmdlist.selectNext()
|
||||
e.preventDefault()
|
||||
when 13
|
||||
e.preventDefault()
|
||||
@selectCommand()
|
||||
else
|
||||
text = @searchbox.value
|
||||
@cmdlist.set "data", (v for v in @data.child) if text.length is 2
|
||||
return if text.length < 3
|
||||
result = []
|
||||
term = new RegExp text, 'i'
|
||||
result.push v for v in @data.child when v.text.match term
|
||||
@cmdlist.set "data", result
|
||||
|
||||
|
||||
selectCommand: () ->
|
||||
el = @cmdlist.get "selectedItem"
|
||||
return unless el
|
||||
el.set "selected", false
|
||||
result = false
|
||||
result = @handle { data: { item: el } } if @handle
|
||||
return @quit() unless result
|
||||
|
||||
CommandPalette.scheme = """
|
||||
<afx-app-window data-id = "cmd-win"
|
||||
apptitle="" minimizable="false"
|
||||
resizable = "false" width="200" height="200">
|
||||
<afx-vbox>
|
||||
<input data-height="25" type = "text" data-id="searchbox"/>
|
||||
<afx-list-view data-id="container"></afx-list-view>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
||||
"""
|
@ -1,176 +0,0 @@
|
||||
# import the CodePad application module
|
||||
App = this.OS.APP.CodePad
|
||||
|
||||
# define the extension
|
||||
class App.extensions.AntOSDK extends App.BaseExtension
|
||||
constructor: (app) ->
|
||||
super app
|
||||
|
||||
# public functions
|
||||
create: () ->
|
||||
@app.openDialog("FileDialog", {
|
||||
title: "__(New Project at)",
|
||||
file: { basename: __("ProjectName") },
|
||||
mimes: ["dir"]
|
||||
}).then (d) =>
|
||||
@mktpl d.file.path, d.name, true
|
||||
|
||||
init: () ->
|
||||
dir = @app.currdir
|
||||
return @create() unless dir and dir.basename
|
||||
dir.read()
|
||||
.then (d) =>
|
||||
return @notify __("Cannot read folder: {0}", dir.path) if d.error
|
||||
return @notify __("The folder is not empty: {0}", dir.path) unless d.result.length is 0
|
||||
@mktpl dir.parent().path, dir.basename
|
||||
|
||||
buildnrun: () ->
|
||||
@metadata("project.json").then (meta) =>
|
||||
@build(meta, true).then () =>
|
||||
@run(meta).catch (e) => @error __("Unable to run project"), e
|
||||
.catch (e) =>
|
||||
@error __("Unable to build project"), e
|
||||
.catch (e) => @error __("Unable to read meta-data"), e
|
||||
|
||||
release: () ->
|
||||
@metadata("project.json").then (meta) =>
|
||||
@build(meta, false).then () =>
|
||||
@mkar("#{meta.root}/build/debug", "#{meta.root}/build/release/#{meta.name}.zip")
|
||||
.then () ->
|
||||
.catch (e) => @error __("Unable to create package archive"), e
|
||||
.catch (e) =>
|
||||
@error __("Unable to build project"), e
|
||||
.catch (e) => @error __("Unable to read meta-data"), e
|
||||
|
||||
|
||||
# private functions
|
||||
mktpl: (path, name, flag) ->
|
||||
rpath = "#{path}/#{name}"
|
||||
dirs = [
|
||||
"#{rpath}/javascripts",
|
||||
"#{rpath}/css",
|
||||
"#{rpath}/coffees",
|
||||
"#{rpath}/assets"
|
||||
]
|
||||
dirs.unshift rpath if flag
|
||||
files = [
|
||||
["templates/sdk-main.tpl", "#{rpath}/coffees/main.coffee"],
|
||||
["templates/sdk-package.tpl", "#{rpath}/package.json"],
|
||||
["templates/sdk-project.tpl", "#{rpath}/project.json"],
|
||||
["templates/sdk-README.tpl", "#{rpath}/README.md"],
|
||||
["templates/sdk-scheme.tpl", "#{rpath}/assets/scheme.html"]
|
||||
]
|
||||
@mkdirAll dirs
|
||||
.then () =>
|
||||
@mkfileAll(files, path, name)
|
||||
.then () =>
|
||||
@app.currdir = rpath.asFileHandle()
|
||||
@app.initSideBar()
|
||||
@app.openFile "#{rpath}/README.md".asFileHandle()
|
||||
.catch (e) => @error __("Unable to create template files"), e
|
||||
.catch (e) => @error __("Unable to create project directory"), e
|
||||
|
||||
verify: (list) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if list.length is 0
|
||||
file = (list.splice 0, 1)[0].asFileHandle()
|
||||
@notify __("Verifying: {0}", file.path)
|
||||
file.read().then (data) =>
|
||||
try
|
||||
CoffeeScript.nodes data
|
||||
@verify list
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
catch ex
|
||||
reject __e ex
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
compile: (meta) ->
|
||||
new Promise (resolve, reject) =>
|
||||
@import([
|
||||
"#{@basedir()}/coffeescript.js",
|
||||
"#{@basedir()}/terser.min.js"
|
||||
]).then () =>
|
||||
list = ("#{meta.root}/#{v}" for v in meta.coffees)
|
||||
@verify((f for f in list)).then () =>
|
||||
@cat(list).then (code) =>
|
||||
jsrc = CoffeeScript.compile code
|
||||
@notify __("Compiled successful")
|
||||
resolve jsrc
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
build: (meta, debug) ->
|
||||
dirs = [
|
||||
"#{meta.root}/build",
|
||||
"#{meta.root}/build/debug",
|
||||
"#{meta.root}/build/release"
|
||||
]
|
||||
new Promise (resolve, reject) =>
|
||||
@mkdirAll(dirs).then =>
|
||||
@compile(meta).then (src) =>
|
||||
@cat ("#{meta.root}/#{v}" for v in meta.javascripts), src
|
||||
.then (jsrc) ->
|
||||
new Promise (r, e) ->
|
||||
code = jsrc
|
||||
if not debug
|
||||
options = {
|
||||
toplevel: true,
|
||||
compress: {
|
||||
passes: 3,
|
||||
#pure_getters: true,
|
||||
#unsafe: true,
|
||||
},
|
||||
mangle: true,
|
||||
output: {
|
||||
#beautify: true,
|
||||
},
|
||||
}
|
||||
result = Terser.minify(jsrc, options)
|
||||
if result.error
|
||||
@notify __("Unable to minify code: {0}", result.error)
|
||||
else
|
||||
code = result.code
|
||||
"#{meta.root}/build/debug/main.js"
|
||||
.asFileHandle()
|
||||
.setCache code
|
||||
.write("text/plain")
|
||||
.then (d) ->
|
||||
r()
|
||||
.catch (ex) -> e __e ex
|
||||
.then () =>
|
||||
new Promise (r, e) =>
|
||||
@cat ("#{meta.root}/#{v}" for v in meta.css), ""
|
||||
.then (txt) ->
|
||||
return r() if txt is ""
|
||||
"#{meta.root}/build/debug/main.css"
|
||||
.asFileHandle()
|
||||
.setCache txt
|
||||
.write("text/plain")
|
||||
.then (d) ->
|
||||
r()
|
||||
.catch (ex) -> e __e ex
|
||||
.then () =>
|
||||
@copy ("#{meta.root}/#{v}" for v in meta.copies), "#{meta.root}/build/debug"
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
run: (meta) ->
|
||||
"#{meta.root}/build/debug/package.json"
|
||||
.asFileHandle()
|
||||
.read("json")
|
||||
.then (v) =>
|
||||
v.text = v.name
|
||||
v.path = "#{meta.root}/build/debug"
|
||||
v.filename = meta.name
|
||||
v.type = "app"
|
||||
v.mime = "antos/app"
|
||||
v.icon = "#{v.path}/#{v.icon}" if v.icon
|
||||
v.iconclass = "fa fa-adn" unless v.iconclass or v.icon
|
||||
@notify __("Installing...")
|
||||
@app.systemsetting.system.packages[meta.name] = v
|
||||
@notify __("Running {0}...", meta.name)
|
||||
@app._gui.forceLaunch meta.name
|
@ -1,217 +0,0 @@
|
||||
# import the CodePad application module
|
||||
App = this.OS.APP.CodePad
|
||||
|
||||
# define the extension
|
||||
class App.extensions.ExtensionMaker extends App.BaseExtension
|
||||
constructor: (app) ->
|
||||
super app
|
||||
|
||||
# public functions
|
||||
create: () ->
|
||||
@app.openDialog("FileDialog", {
|
||||
title: "__(New CodePad extension at)",
|
||||
file: { basename: __("ExtensionName") },
|
||||
mimes: ["dir"]
|
||||
}).then (d) =>
|
||||
@mktpl d.file.path, d.name
|
||||
|
||||
buildnrun: () ->
|
||||
@metadata("extension.json").then (meta) =>
|
||||
@build(meta).then () =>
|
||||
@run(meta).catch (e) => @error __("Unable to run extension"), e
|
||||
.catch (e) =>
|
||||
@error __("Unable to build extension"), e
|
||||
.catch (e) => @error __("Unable to read meta-data"), e
|
||||
|
||||
release: () ->
|
||||
@metadata("extension.json").then (meta) =>
|
||||
@build(meta).then () =>
|
||||
@mkar("#{meta.root}/build/debug",
|
||||
"#{meta.root}/build/release/#{meta.meta.name}.zip")
|
||||
.then () ->
|
||||
.catch (e) => @error __("Unable to create archive"), e
|
||||
.catch (e) =>
|
||||
@error __("Unable to build extension"), e
|
||||
.catch (e) => @error __("Unable to read meta-data"), e
|
||||
|
||||
install: () ->
|
||||
@app.openDialog("FileDialog", {
|
||||
title: "__(Select extension archive)",
|
||||
mimes: [".*/zip"]
|
||||
}).then (d) =>
|
||||
@installZip d.file.path
|
||||
.then () =>
|
||||
@notify __("Extension installed")
|
||||
@app.loadExtensionMetaData()
|
||||
.catch (e) => @error __("Unable to install extension"), e
|
||||
# private functions
|
||||
mktpl: (path, name) ->
|
||||
rpath = "#{path}/#{name}"
|
||||
dirs = [
|
||||
rpath,
|
||||
"#{rpath}/build",
|
||||
"#{rpath}/build/release",
|
||||
"#{rpath}/build/debug"
|
||||
]
|
||||
files = [
|
||||
["templates/ext-main.tpl", "#{rpath}/#{name}.coffee"],
|
||||
["templates/ext-extension.tpl", "#{rpath}/extension.json"],
|
||||
]
|
||||
@mkdirAll dirs
|
||||
.then () =>
|
||||
@mkfileAll(files, path, name)
|
||||
.then () =>
|
||||
@app.currdir = rpath.asFileHandle()
|
||||
@app.initSideBar()
|
||||
@app.openFile "#{rpath}/#{name}.coffee".asFileHandle()
|
||||
.catch (e) => @error __("Unable to create extension template"), e
|
||||
.catch (e) => @error __("Unable to create extension directories"), e
|
||||
|
||||
|
||||
verify: (list) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if list.length is 0
|
||||
file = (list.splice 0, 1)[0].asFileHandle()
|
||||
@notify __("Verifying: {0}", file.path)
|
||||
file.read().then (data) =>
|
||||
try
|
||||
CoffeeScript.nodes data
|
||||
@verify list
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
catch ex
|
||||
reject __e ex
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
compile: (meta) ->
|
||||
new Promise (resolve, reject) =>
|
||||
@import(["#{@basedir()}/coffeescript.js"]).then () =>
|
||||
list = ("#{meta.root}/#{v}" for v in meta.coffees)
|
||||
@verify((f for f in list)).then () =>
|
||||
@cat(list).then (code) =>
|
||||
jsrc = CoffeeScript.compile code
|
||||
@notify __("Compiled successful")
|
||||
resolve jsrc
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
build: (meta) ->
|
||||
new Promise (resolve, reject) =>
|
||||
@compile(meta).then (src) =>
|
||||
@cat ("#{meta.root}/#{v}" for v in meta.javascripts), src
|
||||
.then (jsrc) ->
|
||||
new Promise (r, e) ->
|
||||
"#{meta.root}/build/debug/#{meta.meta.name}.js"
|
||||
.asFileHandle()
|
||||
.setCache jsrc
|
||||
.write("text/plain")
|
||||
.then (d) ->
|
||||
r()
|
||||
.catch (ex) -> e __e ex
|
||||
.then () ->
|
||||
new Promise (r, e) ->
|
||||
"#{meta.root}/build/debug/extension.json"
|
||||
.asFileHandle()
|
||||
.setCache meta.meta
|
||||
.write("object")
|
||||
.then (data) ->
|
||||
r data
|
||||
.catch (ex) -> e __e ex
|
||||
.then () =>
|
||||
@copy ("#{meta.root}/#{v}" for v in meta.copies), "#{meta.root}/build/debug"
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
run: (meta) ->
|
||||
new Promise (resolve, reject) =>
|
||||
path = "#{meta.root}/build/debug/#{meta.meta.name}.js"
|
||||
delete @app._api.shared[path] if @app._api.shared[path]
|
||||
@app._api.requires path
|
||||
.then () =>
|
||||
if @app.extensions[meta.meta.name]
|
||||
@app.extensions[meta.meta.name].child = []
|
||||
@app.extensions[meta.meta.name].addAction v for v in meta.meta.actions
|
||||
else
|
||||
@app.extensions[meta.meta.name] = new App.CMDMenu meta.meta.text
|
||||
@app.extensions[meta.meta.name].name = meta.meta.name
|
||||
@app.extensions[meta.meta.name].addAction v for v in meta.meta.actions
|
||||
@app.spotlight.addAction @app.extensions[meta.meta.name]
|
||||
@app.extensions[meta.meta.name].onchildselect (e) =>
|
||||
@app.loadAndRunExtensionAction e.data.item.get "data"
|
||||
@app.spotlight.run @app
|
||||
resolve()
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
|
||||
installExtension: (files, zip) ->
|
||||
new Promise (resolve, reject) =>
|
||||
idx = files.indexOf "extension.json"
|
||||
reject(@app._api.throwe __("No meta-data found")) if idx < 0
|
||||
metafile = (files.splice idx, 1)[0]
|
||||
# read the meta file
|
||||
zip.file(metafile).async("uint8array").then (d) =>
|
||||
meta = JSON.parse(new TextDecoder("utf-8").decode(d))
|
||||
@installFiles files, zip, meta
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
installFiles: (files, zip, meta) ->
|
||||
return @installMeta(meta) if files.length is 0
|
||||
new Promise (resolve, reject) =>
|
||||
file = (files.splice 0, 1)[0]
|
||||
path = "#{@basedir()}/#{file}"
|
||||
zip.file(file).async("uint8array").then (d) =>
|
||||
path.asFileHandle()
|
||||
.setCache(new Blob [d], { type: "octet/stream" })
|
||||
.write("text/plain").then (r) =>
|
||||
return reject r.error if r.error
|
||||
@installFiles files, zip, meta
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
installMeta: (meta) ->
|
||||
new Promise (resolve, reject) =>
|
||||
file = "#{@app.meta().path}/extensions.json".asFileHandle()
|
||||
file.read("json").then (data) ->
|
||||
names = (v.name) for v in data
|
||||
idx = name.indexOf meta.name
|
||||
data.splice idx, 1 if idx >= 0
|
||||
data.push meta
|
||||
file.setCache data
|
||||
.write("object")
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
installZip: (path) ->
|
||||
new Promise (resolve, reject) =>
|
||||
@import(["os://scripts/jszip.min.js"]).then () =>
|
||||
path.asFileHandle().read("binary").then (data) =>
|
||||
JSZip.loadAsync(data).then (zip) =>
|
||||
pth = @basedir()
|
||||
dir = []
|
||||
files = []
|
||||
for name, file of zip.files
|
||||
if file.dir
|
||||
dir.push(pth + "/" + name)
|
||||
else
|
||||
files.push name
|
||||
if dir.length > 0
|
||||
@mkdirAll dir
|
||||
.then () =>
|
||||
@installExtension files, zip
|
||||
.then () -> resolve()
|
||||
.catch(e) -> reject(__e e)
|
||||
.catch (e) -> reject __e e
|
||||
else
|
||||
@installExtension files, zip
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject(__e e)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
@ -1,502 +0,0 @@
|
||||
Ant = this
|
||||
|
||||
class CodePad extends this.OS.GUI.BaseApplication
|
||||
constructor: (args) ->
|
||||
super "CodePad", args
|
||||
@currfile = "Untitled".asFileHandle()
|
||||
@currdir = undefined
|
||||
if @args and @args.length > 0
|
||||
if @args[0].type is "dir"
|
||||
@currdir = @args[0].path.asFileHandle()
|
||||
else
|
||||
@currfile = @args[0].path.asFileHandle()
|
||||
@currdir = @currfile.parent()
|
||||
|
||||
main: () ->
|
||||
@extensions = {}
|
||||
@fileview = @find("fileview")
|
||||
@sidebar = @find("sidebar")
|
||||
@tabbar = @find "tabbar"
|
||||
@langstat = @find "langstat"
|
||||
@editorstat = @find "editorstat"
|
||||
|
||||
@fileview.set "fetch", (path) ->
|
||||
new Promise (resolve, reject) ->
|
||||
dir = path
|
||||
dir = path.asFileHandle() if typeof path is "string"
|
||||
dir.read().then (d) ->
|
||||
return reject d.error if d.error
|
||||
resolve d.result
|
||||
.catch (e) -> reject __e e
|
||||
@setup()
|
||||
|
||||
setup: () ->
|
||||
ace.config.set('basePath', '/scripts/ace')
|
||||
ace.require "ace/ext/language_tools"
|
||||
@editor = ace.edit @find("datarea")
|
||||
@editor.setOptions {
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: true,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectedWord: true,
|
||||
behavioursEnabled: true,
|
||||
wrap: true,
|
||||
fontSize: "11pt",
|
||||
showInvisibles: true
|
||||
}
|
||||
#themes = ace.require "ace/ext/themelist"
|
||||
@editor.setTheme "ace/theme/monokai"
|
||||
@modes = ace.require "ace/ext/modelist"
|
||||
@editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
|
||||
@editor.getSession().setUseWrapMode true
|
||||
@editormux = false
|
||||
@editor.on "input", () =>
|
||||
if @editormux
|
||||
@editormux = false
|
||||
return false
|
||||
if not @currfile.dirty
|
||||
@currfile.dirty = true
|
||||
@currfile.text += "*"
|
||||
@tabbar.update()
|
||||
@editor.getSession().selection.on "changeCursor", (e) =>
|
||||
@updateStatus()
|
||||
|
||||
@tabbar.set "ontabselect", (e) =>
|
||||
@selecteTab $(e.data.item).index()
|
||||
@tabbar.set "ontabclose", (e) =>
|
||||
it = e.data.item
|
||||
return false unless it
|
||||
return @closeTab it unless it.get("data").dirty
|
||||
@openDialog("YesNoDialog", {
|
||||
title: __("Close tab"),
|
||||
text: __("Close without saving ?")
|
||||
}).then (d) =>
|
||||
return @closeTab it if d
|
||||
@editor.focus()
|
||||
return false
|
||||
@fileview.set "onfileopen", (e) =>
|
||||
return unless e.data and e.data.path
|
||||
return if e.data.type is "dir"
|
||||
@openFile e.data.path.asFileHandle()
|
||||
|
||||
@fileview.set "onfileselect", (e) =>
|
||||
return unless e.data and e.data.path
|
||||
return if e.data.type is "dir"
|
||||
i = @findTabByFile e.data.path.asFileHandle()
|
||||
return @tabbar.set "selected", i if i isnt -1
|
||||
|
||||
@on "resize", () => @editor.resize()
|
||||
@on "focus", () => @editor.focus()
|
||||
@spotlight = new CMDMenu __("Command palette")
|
||||
@bindKey "ALT-P", () => @spotlight.run @
|
||||
@find("datarea").contextmenuHandle = (e, m) =>
|
||||
m.set "items", [{
|
||||
text: __("Command palete"),
|
||||
onmenuselect: (e) =>
|
||||
@spotlight.run @
|
||||
}]
|
||||
m.show e
|
||||
|
||||
@fileview.contextmenuHandle = (e, m) =>
|
||||
m.set "items", [
|
||||
{ text: "__(New file)", id: "new" },
|
||||
{ text: "__(New folder)", id: "newdir" },
|
||||
{ text: "__(Rename)", id: "rename" },
|
||||
{ text: "__(Delete)", id: "delete" }
|
||||
]
|
||||
m.set "onmenuselect", (e) =>
|
||||
@ctxFileMenuHandle e
|
||||
m.show e
|
||||
|
||||
@bindKey "ALT-N", () => @menuAction "new"
|
||||
@bindKey "ALT-O", () => @menuAction "open"
|
||||
@bindKey "ALT-F", () => @menuAction "opendir"
|
||||
@bindKey "CTRL-S", () => @menuAction "save"
|
||||
@bindKey "ALT-W", () => @menuAction "saveas"
|
||||
|
||||
@fileview.set "ondragndrop", (e) =>
|
||||
src = e.data.from.get("data").path.asFileHandle()
|
||||
des = e.data.to.get("data").path
|
||||
src.move "#{des}/#{src.basename}"
|
||||
.then (d) ->
|
||||
e.data.to.update des
|
||||
e.data.from.get("parent").update src.parent().path
|
||||
.catch (e) => @error __("Unable to move file/folder"), e
|
||||
|
||||
@on "filechange", (data) =>
|
||||
path = data.file.path
|
||||
path = data.file.parent().path if data.type is "file"
|
||||
@fileview.update path
|
||||
|
||||
|
||||
@loadExtensionMetaData()
|
||||
@initCommandPalete()
|
||||
@initSideBar()
|
||||
@openFile @currfile
|
||||
|
||||
openFile: (file) ->
|
||||
#find tab
|
||||
i = @findTabByFile file
|
||||
return @tabbar.set "selected", i if i isnt -1
|
||||
return @newTab file if file.path.toString() is "Untitled"
|
||||
|
||||
file.read()
|
||||
.then (d) =>
|
||||
file.cache = d or ""
|
||||
@newTab file
|
||||
.catch (e) =>
|
||||
@error __("Unable to open: {0}", file.path), e
|
||||
|
||||
findTabByFile: (file) ->
|
||||
lst = @tabbar.get "items"
|
||||
its = ( i for d, i in lst when d.hash() is file.hash() )
|
||||
return -1 if its.length is 0
|
||||
return its[0]
|
||||
|
||||
newTab: (file) ->
|
||||
file.text = if file.basename then file.basename else file.path
|
||||
file.cache = "" unless file.cache
|
||||
file.um = new ace.UndoManager()
|
||||
@currfile.selected = false
|
||||
file.selected = true
|
||||
#console.log cnt
|
||||
@tabbar.push file
|
||||
|
||||
closeTab: (it) ->
|
||||
@tabbar.remove it
|
||||
cnt = @tabbar.get("items").length
|
||||
|
||||
if cnt is 0
|
||||
@openFile "Untitled".asFileHandle()
|
||||
return false
|
||||
@tabbar.set "selected", cnt - 1
|
||||
return false
|
||||
|
||||
selecteTab: (i) ->
|
||||
#return if i is @tabbar.get "selidx"
|
||||
file = (@tabbar.get "items")[i]
|
||||
return unless file
|
||||
@scheme.set "apptitle", file.text.toString()
|
||||
#return if file is @currfile
|
||||
if @currfile isnt file
|
||||
@currfile.cache = @editor.getValue()
|
||||
@currfile.cursor = @editor.selection.getCursor()
|
||||
@currfile.selected = false
|
||||
@currfile = file
|
||||
|
||||
if not file.langmode
|
||||
if file.path.toString() isnt "Untitled"
|
||||
m = @modes.getModeForPath(file.path)
|
||||
file.langmode = { caption: m.caption, mode: m.mode }
|
||||
else
|
||||
file.langmode = { caption: "Text", mode: "ace/mode/text" }
|
||||
@editormux = true
|
||||
@editor.getSession().setUndoManager new ace.UndoManager()
|
||||
@editor.setValue file.cache, -1
|
||||
@editor.getSession().setMode file.langmode.mode
|
||||
if file.cursor
|
||||
@editor.renderer.scrollCursorIntoView {
|
||||
row: file.cursor.row, column: file.cursor.column
|
||||
}, 0.5
|
||||
@editor.selection.moveTo file.cursor.row, file.cursor.column
|
||||
@editor.getSession().setUndoManager file.um
|
||||
@updateStatus()
|
||||
@editor.focus()
|
||||
|
||||
updateStatus: () ->
|
||||
c = @editor.session.selection.getCursor()
|
||||
l = @editor.session.getLength()
|
||||
@editorstat.set "text", __("Row {0}, col {1}, lines: {2}", c.row + 1, c.column + 1, l)
|
||||
@langstat.set "text", @currfile.langmode.caption
|
||||
|
||||
initSideBar: () ->
|
||||
if @currdir
|
||||
$(@sidebar).show()
|
||||
@fileview.set "path", @currdir.path
|
||||
else
|
||||
$(@sidebar).hide()
|
||||
@trigger "resize"
|
||||
|
||||
addAction: (action) ->
|
||||
@spotlight.addAction action
|
||||
@
|
||||
|
||||
addActions: (list) ->
|
||||
@spotlight.addActions list
|
||||
@
|
||||
|
||||
initCommandPalete: () ->
|
||||
themes = ace.require "ace/ext/themelist"
|
||||
cmdtheme = new CMDMenu __("Change theme")
|
||||
cmdtheme.addAction { text: v.caption, theme: v.theme } for k, v of themes.themesByName
|
||||
cmdtheme.onchildselect (d, r) ->
|
||||
data = d.data.item.get("data")
|
||||
r.editor.setTheme data.theme
|
||||
r.editor.focus()
|
||||
@spotlight.addAction cmdtheme
|
||||
cmdmode = new CMDMenu __("Change language mode")
|
||||
cmdmode.addAction { text: v.caption, mode: v.mode } for v in @modes.modes
|
||||
cmdmode.onchildselect (d, r) ->
|
||||
data = d.data.item.get("data")
|
||||
r.editor.session.setMode data.mode
|
||||
r.currfile.langmode = { caption: data.text, mode: data.mode }
|
||||
r.updateStatus()
|
||||
r.editor.focus()
|
||||
@spotlight.addAction cmdmode
|
||||
@addAction CMDMenu.fromMenu @fileMenu()
|
||||
|
||||
loadExtensionMetaData: () ->
|
||||
"#{@meta().path}/extensions.json"
|
||||
.asFileHandle()
|
||||
.read("json")
|
||||
.then (d) =>
|
||||
for ext in d
|
||||
if @extensions[ext.name]
|
||||
@extensions[ext.name].child = []
|
||||
@extensions[ext.name].addAction v for v in ext.actions
|
||||
else
|
||||
@extensions[ext.name] = new CMDMenu ext.text
|
||||
@extensions[ext.name].name = ext.name
|
||||
@extensions[ext.name].addAction v for v in ext.actions
|
||||
@spotlight.addAction @extensions[ext.name]
|
||||
@extensions[ext.name].onchildselect (e) =>
|
||||
@loadAndRunExtensionAction e.data.item.get "data"
|
||||
.catch (e) =>
|
||||
@error __("Cannot load extension meta data"), e
|
||||
|
||||
runExtensionAction: (name, action) ->
|
||||
return @error __("Unable to find extension: {0}", name) unless CodePad.extensions[name]
|
||||
ext = new CodePad.extensions[name](@)
|
||||
return @error __("Unable to find action: {0}", action) unless ext[action]
|
||||
ext.preload()
|
||||
.then () ->
|
||||
ext[action]()
|
||||
.catch (e) =>
|
||||
@error __("Unable to preload extension"), e
|
||||
|
||||
loadAndRunExtensionAction: (data) ->
|
||||
name = data.parent.name
|
||||
action = data.name
|
||||
#verify if the extension is load
|
||||
if not CodePad.extensions[name]
|
||||
#load the extension
|
||||
path = "#{@meta().path}/extensions/#{name}.js"
|
||||
@_api.requires path
|
||||
.then () => @runExtensionAction name, action
|
||||
.catch (e) =>
|
||||
@error __("unable to load extension: {0}", name), e
|
||||
else
|
||||
@runExtensionAction name, action
|
||||
|
||||
fileMenu: () ->
|
||||
{
|
||||
text: __("File"),
|
||||
child: [
|
||||
{ text: __("New"), dataid: "new", shortcut: "A-N" },
|
||||
{ text: __("Open"), dataid: "open", shortcut: "A-O" },
|
||||
{ text: __("Open Folder"), dataid: "opendir", shortcut: "A-F" },
|
||||
{ text: __("Save"), dataid: "save", shortcut: "C-S" },
|
||||
{ text: __("Save as"), dataid: "saveas", shortcut: "A-W" }
|
||||
],
|
||||
onchildselect: (e, r) =>
|
||||
@menuAction e.data.item.get("data").dataid, r
|
||||
}
|
||||
|
||||
ctxFileMenuHandle: (e) ->
|
||||
el = e.data.item
|
||||
return unless el
|
||||
data = el.get("data")
|
||||
return unless data
|
||||
file = @fileview.get "selectedFile"
|
||||
dir = @currdir
|
||||
dir = file.path.asFileHandle() if file and file.type is "dir"
|
||||
dir = file.path.asFileHandle().parent() if file and file.type is "file"
|
||||
|
||||
switch data.id
|
||||
when "new"
|
||||
return unless dir
|
||||
@openDialog("PromptDialog", {
|
||||
title: "__(New file)",
|
||||
label: "__(File name)"
|
||||
})
|
||||
.then (d) =>
|
||||
fp = "#{dir.path}/#{d}".asFileHandle()
|
||||
fp.write("text/plain")
|
||||
.then (r) =>
|
||||
@fileview.update dir.path
|
||||
.catch (e) =>
|
||||
@error __("Fail to create: {0}", e.stack), e
|
||||
|
||||
when "newdir"
|
||||
return unless dir
|
||||
@openDialog("PromptDialog", {
|
||||
title: "__(New folder)",
|
||||
label: "__(Folder name)"
|
||||
})
|
||||
.then (d) =>
|
||||
dir.mk(d)
|
||||
.then (r) =>
|
||||
@fileview.update dir.path
|
||||
.catch (e) =>
|
||||
@error __("Fail to create: {0}", dir.path), e
|
||||
|
||||
when "rename"
|
||||
return unless file
|
||||
@openDialog("PromptDialog", {
|
||||
title: "__(Rename)",
|
||||
label: "__(File name)",
|
||||
value: file.filename
|
||||
})
|
||||
.then (d) =>
|
||||
return if d is file.filename
|
||||
file = file.path.asFileHandle()
|
||||
dir = file.parent()
|
||||
file.move "#{dir.path}/#{d}"
|
||||
.then (r) =>
|
||||
@fileview.update dir.path
|
||||
.catch (e) =>
|
||||
@error __("Fail to rename: {0}", file.path), e
|
||||
|
||||
when "delete"
|
||||
return unless file
|
||||
@openDialog("YesNoDialog", {
|
||||
title: "__(Delete)",
|
||||
iconclass: "fa fa-question-circle",
|
||||
text: __("Do you really want to delete: {0}?", file.filename)
|
||||
})
|
||||
.then (d) =>
|
||||
return unless d
|
||||
file = file.path.asFileHandle()
|
||||
dir = file.parent()
|
||||
file.remove()
|
||||
.then (r) =>
|
||||
@fileview.update dir.path
|
||||
.catch (e) =>
|
||||
@error __("Fail to delete: {0}", file.path), e
|
||||
|
||||
else
|
||||
|
||||
|
||||
save: (file) ->
|
||||
file.write("text/plain")
|
||||
.then (d) =>
|
||||
file.dirty = false
|
||||
file.text = file.basename
|
||||
@tabbar.update()
|
||||
@scheme.set "apptitle", "#{@currfile.basename}"
|
||||
.catch (e) => @error __("Unable to save file: {0}", file.path), e
|
||||
|
||||
|
||||
saveAs: () ->
|
||||
@openDialog("FileDialog", {
|
||||
title: __("Save as"),
|
||||
file: @currfile
|
||||
})
|
||||
.then (f) =>
|
||||
d = f.file.path.asFileHandle()
|
||||
d = d.parent() if f.file.type is "file"
|
||||
@currfile.setPath "#{d.path}/#{f.name}"
|
||||
@save @currfile
|
||||
|
||||
menuAction: (dataid, r) ->
|
||||
me = @
|
||||
me = r if r
|
||||
switch dataid
|
||||
when "new"
|
||||
me.openFile "Untitled".asFileHandle()
|
||||
when "open"
|
||||
me.openDialog("FileDialog", {
|
||||
title: __("Open file"),
|
||||
mimes: (v for v in me.meta().mimes when v isnt "dir")
|
||||
})
|
||||
.then (f) ->
|
||||
me.openFile f.file.path.asFileHandle()
|
||||
when "opendir"
|
||||
me.openDialog("FileDialog", {
|
||||
title: __("Open folder"),
|
||||
mimes: ["dir"]
|
||||
})
|
||||
.then (f) ->
|
||||
me.currdir = f.file.path.asFileHandle()
|
||||
me.initSideBar()
|
||||
when "save"
|
||||
me.currfile.cache = me.editor.getValue()
|
||||
return me.save me.currfile if me.currfile.basename
|
||||
me.saveAs()
|
||||
when "saveas"
|
||||
me.currfile.cache = me.editor.getValue()
|
||||
me.saveAs()
|
||||
else
|
||||
console.log dataid
|
||||
|
||||
cleanup: (evt) ->
|
||||
dirties = ( v for v in @tabbar.get "items" when v.dirty )
|
||||
return if dirties.length is 0
|
||||
evt.preventDefault()
|
||||
@.openDialog("YesNoDialog", {
|
||||
title: "__(Quit)",
|
||||
text: __("Ignore all unsaved files: {0} ?", (v.filename() for v in dirties).join ", " )
|
||||
}).then (d) =>
|
||||
if d
|
||||
v.dirty = false for v in dirties
|
||||
@quit()
|
||||
|
||||
menu: () ->
|
||||
menu = [
|
||||
@fileMenu()
|
||||
{
|
||||
text: "__(View)",
|
||||
child: [
|
||||
{ text: "__(Command Palette)", dataid: "cmdpalette", shortcut: "A-P" }
|
||||
],
|
||||
onchildselect: (e, r) =>
|
||||
@spotlight.run @
|
||||
}
|
||||
]
|
||||
menu
|
||||
|
||||
class CMDMenu
|
||||
constructor: (@text, @shortcut) ->
|
||||
@child = []
|
||||
@parent = undefined
|
||||
@select = (e) ->
|
||||
|
||||
addAction: (v) ->
|
||||
v.parent = @
|
||||
@child.push v
|
||||
@
|
||||
|
||||
addActions: (list) ->
|
||||
@addAction v for v in list
|
||||
|
||||
onchildselect: (f) ->
|
||||
@select = f
|
||||
@
|
||||
|
||||
run: (root) ->
|
||||
root.openDialog(new CommandPalette(), @)
|
||||
.then (d) =>
|
||||
data = d.data.item.get("data")
|
||||
return data.run root if data.run
|
||||
@select d, root
|
||||
|
||||
CMDMenu.fromMenu = (mn) ->
|
||||
m = new CMDMenu mn.text, mn.shortcut
|
||||
m.onchildselect mn.onchildselect
|
||||
for v in mn.child
|
||||
if v.child
|
||||
m.addAction CMDMenu.fromMenu v
|
||||
else
|
||||
m.addAction v
|
||||
m
|
||||
|
||||
CodePad.CMDMenu = CMDMenu
|
||||
|
||||
CodePad.dependencies = [
|
||||
"os://scripts/ace/ace.js",
|
||||
"os://scripts/ace/ext-language_tools.js",
|
||||
"os://scripts/ace/ext-modelist.js",
|
||||
"os://scripts/ace/ext-themelist.js"
|
||||
]
|
||||
this.OS.register "CodePad", CodePad
|
222
src/packages/CodePad/extensions/AntOSDK.js
Normal file
222
src/packages/CodePad/extensions/AntOSDK.js
Normal file
@ -0,0 +1,222 @@
|
||||
(function() {
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// import the CodePad application module
|
||||
const App = this.OS.APP.CodePad;
|
||||
|
||||
// define the extension
|
||||
App.extensions.AntOSDK = class AntOSDK extends App.BaseExtension {
|
||||
constructor(app) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
// public functions
|
||||
create() {
|
||||
return this.app.openDialog("FileDialog", {
|
||||
title: "__(New Project at)",
|
||||
file: { basename: __("ProjectName") },
|
||||
mimes: ["dir"]
|
||||
}).then(d => {
|
||||
return this.mktpl(d.file.path, d.name, true);
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
const dir = this.app.currdir;
|
||||
if (!dir || !dir.basename) { return this.create(); }
|
||||
return dir.read()
|
||||
.then(d => {
|
||||
if (d.error) { return this.notify(__("Cannot read folder: {0}", dir.path)); }
|
||||
if (d.result.length !== 0) { return this.notify(__("The folder is not empty: {0}", dir.path)); }
|
||||
return this.mktpl(dir.parent().path, dir.basename);
|
||||
});
|
||||
}
|
||||
|
||||
buildnrun() {
|
||||
return this.metadata("project.json").then(meta => {
|
||||
return this.build(meta, true).then(() => {
|
||||
return this.run(meta).catch(e => this.error(__("Unable to run project"), e));
|
||||
}).catch(e => {
|
||||
return this.error(__("Unable to build project"), e);
|
||||
});
|
||||
}).catch(e => this.error(__("Unable to read meta-data"), e));
|
||||
}
|
||||
|
||||
release() {
|
||||
return this.metadata("project.json").then(meta => {
|
||||
return this.build(meta, false).then(() => {
|
||||
return this.mkar(`${meta.root}/build/debug`, `${meta.root}/build/release/${meta.name}.zip`)
|
||||
.catch(e => this.error(__("Unable to create package archive"), e));
|
||||
}).catch(e => {},
|
||||
this.error(__("Unable to build project"), e)
|
||||
);
|
||||
}).catch(e => this.error(__("Unable to read meta-data"), e));
|
||||
}
|
||||
|
||||
|
||||
// private functions
|
||||
mktpl(path, name, flag) {
|
||||
const rpath = `${path}/${name}`;
|
||||
const dirs = [
|
||||
`${rpath}/javascripts`,
|
||||
`${rpath}/css`,
|
||||
`${rpath}/coffees`,
|
||||
`${rpath}/assets`
|
||||
];
|
||||
if (flag) { dirs.unshift(rpath); }
|
||||
const files = [
|
||||
["templates/sdk-main.tpl", `${rpath}/coffees/main.coffee`],
|
||||
["templates/sdk-package.tpl", `${rpath}/package.json`],
|
||||
["templates/sdk-project.tpl", `${rpath}/project.json`],
|
||||
["templates/sdk-README.tpl", `${rpath}/README.md`],
|
||||
["templates/sdk-scheme.tpl", `${rpath}/assets/scheme.html`]
|
||||
];
|
||||
return this.mkdirAll(dirs)
|
||||
.then(() => {
|
||||
return this.mkfileAll(files, path, name)
|
||||
.then(() => {
|
||||
this.app.currdir = rpath.asFileHandle();
|
||||
this.app.initSideBar();
|
||||
return this.app.openFile(`${rpath}/README.md`.asFileHandle());
|
||||
}).catch(e => this.error(__("Unable to create template files"), e));
|
||||
}).catch(e => this.error(__("Unable to create project directory"), e));
|
||||
}
|
||||
|
||||
verify(list) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(); }
|
||||
const file = (list.splice(0, 1))[0].asFileHandle();
|
||||
this.notify(__("Verifying: {0}", file.path));
|
||||
return file.read().then(data => {
|
||||
try {
|
||||
CoffeeScript.nodes(data);
|
||||
return this.verify(list)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
} catch (ex) {
|
||||
return reject(__e(ex));
|
||||
}
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
compile(meta) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.import([
|
||||
`${this.basedir()}/coffeescript.js`,
|
||||
`${this.basedir()}/terser.min.js`
|
||||
]).then(() => {
|
||||
const list = (Array.from(meta.coffees).map((v) => `${meta.root}/${v}`));
|
||||
return this.verify((Array.from(list))).then(() => {
|
||||
return this.cat(list).then(code => {
|
||||
const jsrc = CoffeeScript.compile(code);
|
||||
this.notify(__("Compiled successful"));
|
||||
return resolve(jsrc);
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
build(meta, debug) {
|
||||
const dirs = [
|
||||
`${meta.root}/build`,
|
||||
`${meta.root}/build/debug`,
|
||||
`${meta.root}/build/release`
|
||||
];
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.mkdirAll(dirs).then(() => {
|
||||
return this.compile(meta).then(src => {
|
||||
let v;
|
||||
return this.cat(((() => {
|
||||
const result = [];
|
||||
for (v of Array.from(meta.javascripts)) { result.push(`${meta.root}/${v}`);
|
||||
}
|
||||
return result;
|
||||
})()), src)
|
||||
.then(jsrc => new Promise(function(r, e) {
|
||||
let code = jsrc;
|
||||
if (!debug) {
|
||||
const options = {
|
||||
toplevel: true,
|
||||
compress: {
|
||||
passes: 3,
|
||||
//pure_getters: true,
|
||||
//unsafe: true,
|
||||
},
|
||||
mangle: true,
|
||||
output: {
|
||||
//beautify: true,
|
||||
},
|
||||
};
|
||||
const result = Terser.minify(jsrc, options);
|
||||
if (result.error) {
|
||||
this.notify(__("Unable to minify code: {0}", result.error));
|
||||
} else {
|
||||
({
|
||||
code
|
||||
} = result);
|
||||
}
|
||||
}
|
||||
return `${meta.root}/build/debug/main.js`
|
||||
.asFileHandle()
|
||||
.setCache(code)
|
||||
.write("text/plain")
|
||||
.then(d => r()).catch(ex => e(__e(ex)));
|
||||
})).then(() => {
|
||||
return new Promise((r, e) => {
|
||||
return this.cat(((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(meta.css)) { result1.push(`${meta.root}/${v}`);
|
||||
}
|
||||
return result1;
|
||||
})()), "")
|
||||
.then(function(txt) {
|
||||
if (txt === "") { return r(); }
|
||||
return `${meta.root}/build/debug/main.css`
|
||||
.asFileHandle()
|
||||
.setCache(txt)
|
||||
.write("text/plain")
|
||||
.then(d => r()).catch(ex => e(__e(ex)));
|
||||
});
|
||||
});
|
||||
}).then(() => {
|
||||
return this.copy(((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(meta.copies)) { result1.push(`${meta.root}/${v}`);
|
||||
}
|
||||
return result1;
|
||||
})()), `${meta.root}/build/debug`);
|
||||
}).then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
run(meta) {
|
||||
return `${meta.root}/build/debug/package.json`
|
||||
.asFileHandle()
|
||||
.read("json")
|
||||
.then(v => {
|
||||
v.text = v.name;
|
||||
v.path = `${meta.root}/build/debug`;
|
||||
v.filename = meta.name;
|
||||
v.type = "app";
|
||||
v.mime = "antos/app";
|
||||
if (v.icon) { v.icon = `${v.path}/${v.icon}`; }
|
||||
if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; }
|
||||
this.notify(__("Installing..."));
|
||||
this.app.systemsetting.system.packages[meta.name] = v;
|
||||
this.notify(__("Running {0}...", meta.name));
|
||||
return this.app._gui.forceLaunch(meta.name);
|
||||
});
|
||||
}
|
||||
};
|
||||
}).call(this);
|
261
src/packages/CodePad/extensions/ExtensionMaker.js
Normal file
261
src/packages/CodePad/extensions/ExtensionMaker.js
Normal file
@ -0,0 +1,261 @@
|
||||
(function() {
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// import the CodePad application module
|
||||
const App = this.OS.APP.CodePad;
|
||||
|
||||
// define the extension
|
||||
App.extensions.ExtensionMaker = class ExtensionMaker extends App.BaseExtension {
|
||||
constructor(app) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
// public functions
|
||||
create() {
|
||||
return this.app.openDialog("FileDialog", {
|
||||
title: "__(New CodePad extension at)",
|
||||
file: { basename: __("ExtensionName") },
|
||||
mimes: ["dir"]
|
||||
}).then(d => {
|
||||
return this.mktpl(d.file.path, d.name);
|
||||
});
|
||||
}
|
||||
|
||||
buildnrun() {
|
||||
return this.metadata("extension.json").then(meta => {
|
||||
return this.build(meta).then(() => {
|
||||
return this.run(meta).catch(e => this.error(__("Unable to run extension"), e));
|
||||
}).catch(e => {
|
||||
return this.error(__("Unable to build extension"), e);
|
||||
});
|
||||
}).catch(e => this.error(__("Unable to read meta-data"), e));
|
||||
}
|
||||
|
||||
release() {
|
||||
return this.metadata("extension.json").then(meta => {
|
||||
return this.build(meta).then(() => {
|
||||
return this.mkar(`${meta.root}/build/debug`,
|
||||
`${meta.root}/build/release/${meta.meta.name}.zip`)
|
||||
.catch(e => this.error(__("Unable to create archive"), e));
|
||||
}).catch(e => {},
|
||||
this.error(__("Unable to build extension"), e)
|
||||
);
|
||||
}).catch(e => this.error(__("Unable to read meta-data"), e));
|
||||
}
|
||||
|
||||
install() {
|
||||
return this.app.openDialog("FileDialog", {
|
||||
title: "__(Select extension archive)",
|
||||
mimes: [".*/zip"]
|
||||
}).then(d => {
|
||||
return this.installZip(d.file.path)
|
||||
.then(() => {
|
||||
this.notify(__("Extension installed"));
|
||||
return this.app.loadExtensionMetaData();
|
||||
}).catch(e => this.error(__("Unable to install extension"), e));
|
||||
});
|
||||
}
|
||||
// private functions
|
||||
mktpl(path, name) {
|
||||
const rpath = `${path}/${name}`;
|
||||
const dirs = [
|
||||
rpath,
|
||||
`${rpath}/build`,
|
||||
`${rpath}/build/release`,
|
||||
`${rpath}/build/debug`
|
||||
];
|
||||
const files = [
|
||||
["templates/ext-main.tpl", `${rpath}/${name}.coffee`],
|
||||
["templates/ext-extension.tpl", `${rpath}/extension.json`],
|
||||
];
|
||||
return this.mkdirAll(dirs)
|
||||
.then(() => {
|
||||
return this.mkfileAll(files, path, name)
|
||||
.then(() => {
|
||||
this.app.currdir = rpath.asFileHandle();
|
||||
this.app.initSideBar();
|
||||
return this.app.openFile(`${rpath}/${name}.coffee`.asFileHandle());
|
||||
}).catch(e => this.error(__("Unable to create extension template"), e));
|
||||
}).catch(e => this.error(__("Unable to create extension directories"), e));
|
||||
}
|
||||
|
||||
|
||||
verify(list) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(); }
|
||||
const file = (list.splice(0, 1))[0].asFileHandle();
|
||||
this.notify(__("Verifying: {0}", file.path));
|
||||
return file.read().then(data => {
|
||||
try {
|
||||
CoffeeScript.nodes(data);
|
||||
return this.verify(list)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
} catch (ex) {
|
||||
return reject(__e(ex));
|
||||
}
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
compile(meta) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.import([`${this.basedir()}/coffeescript.js`]).then(() => {
|
||||
const list = (Array.from(meta.coffees).map((v) => `${meta.root}/${v}`));
|
||||
return this.verify((Array.from(list))).then(() => {
|
||||
return this.cat(list).then(code => {
|
||||
const jsrc = CoffeeScript.compile(code);
|
||||
this.notify(__("Compiled successful"));
|
||||
return resolve(jsrc);
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
build(meta) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.compile(meta).then(src => {
|
||||
let v;
|
||||
return this.cat(((() => {
|
||||
const result = [];
|
||||
for (v of Array.from(meta.javascripts)) { result.push(`${meta.root}/${v}`);
|
||||
}
|
||||
return result;
|
||||
})()), src)
|
||||
.then(jsrc => new Promise((r, e) => `${meta.root}/build/debug/${meta.meta.name}.js`
|
||||
.asFileHandle()
|
||||
.setCache(jsrc)
|
||||
.write("text/plain")
|
||||
.then(d => r()).catch(ex => e(__e(ex))))).then(() => new Promise((r, e) => `${meta.root}/build/debug/extension.json`
|
||||
.asFileHandle()
|
||||
.setCache(meta.meta)
|
||||
.write("object")
|
||||
.then(data => r(data)).catch(ex => e(__e(ex))))).then(() => {
|
||||
return this.copy(((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(meta.copies)) { result1.push(`${meta.root}/${v}`);
|
||||
}
|
||||
return result1;
|
||||
})()), `${meta.root}/build/debug`);
|
||||
}).then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
run(meta) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const path = `${meta.root}/build/debug/${meta.meta.name}.js`;
|
||||
if (this.app._api.shared[path]) { delete this.app._api.shared[path]; }
|
||||
return this.app._api.requires(path)
|
||||
.then(() => {
|
||||
let v;
|
||||
if (this.app.extensions[meta.meta.name]) {
|
||||
this.app.extensions[meta.meta.name].child = [];
|
||||
for (v of Array.from(meta.meta.actions)) { this.app.extensions[meta.meta.name].addAction(v); }
|
||||
} else {
|
||||
this.app.extensions[meta.meta.name] = new App.CMDMenu(meta.meta.text);
|
||||
this.app.extensions[meta.meta.name].name = meta.meta.name;
|
||||
for (v of Array.from(meta.meta.actions)) { this.app.extensions[meta.meta.name].addAction(v); }
|
||||
this.app.spotlight.addAction(this.app.extensions[meta.meta.name]);
|
||||
this.app.extensions[meta.meta.name].onchildselect(e => {
|
||||
return this.app.loadAndRunExtensionAction(e.data.item.get("data"));
|
||||
});
|
||||
}
|
||||
this.app.spotlight.run(this.app);
|
||||
return resolve();
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
installExtension(files, zip) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const idx = files.indexOf("extension.json");
|
||||
if (idx < 0) { reject(this.app._api.throwe(__("No meta-data found"))); }
|
||||
const metafile = (files.splice(idx, 1))[0];
|
||||
// read the meta file
|
||||
return zip.file(metafile).async("uint8array").then(d => {
|
||||
const meta = JSON.parse(new TextDecoder("utf-8").decode(d));
|
||||
return this.installFiles(files, zip, meta)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
installFiles(files, zip, meta) {
|
||||
if (files.length === 0) { return this.installMeta(meta); }
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = (files.splice(0, 1))[0];
|
||||
const path = `${this.basedir()}/${file}`;
|
||||
return zip.file(file).async("uint8array").then(d => {
|
||||
return path.asFileHandle()
|
||||
.setCache(new Blob([d], { type: "octet/stream" }))
|
||||
.write("text/plain").then(r => {
|
||||
if (r.error) { return reject(r.error); }
|
||||
return this.installFiles(files, zip, meta)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
installMeta(meta) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = `${this.app.meta().path}/extensions.json`.asFileHandle();
|
||||
return file.read("json").then(function(data) {
|
||||
for (let v of Array.from(data)) { const names = (v.name); }
|
||||
const idx = name.indexOf(meta.name);
|
||||
if (idx >= 0) { data.splice(idx, 1); }
|
||||
data.push(meta);
|
||||
return file.setCache(data)
|
||||
.write("object")
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
installZip(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.import(["os://scripts/jszip.min.js"]).then(() => {
|
||||
return path.asFileHandle().read("binary").then(data => {
|
||||
return JSZip.loadAsync(data).then(zip => {
|
||||
const pth = this.basedir();
|
||||
const dir = [];
|
||||
const files = [];
|
||||
for (let name in zip.files) {
|
||||
const file = zip.files[name];
|
||||
if (file.dir) {
|
||||
dir.push(pth + "/" + name);
|
||||
} else {
|
||||
files.push(name);
|
||||
}
|
||||
}
|
||||
if (dir.length > 0) {
|
||||
return this.mkdirAll(dir)
|
||||
.then(() => {
|
||||
return this.installExtension(files, zip)
|
||||
.then(() => resolve())
|
||||
.catch(e)(() => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
} else {
|
||||
return this.installExtension(files, zip)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
};
|
||||
}).call(this);
|
628
src/packages/CodePad/main.js
Normal file
628
src/packages/CodePad/main.js
Normal file
@ -0,0 +1,628 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const Ant = this;
|
||||
|
||||
class CodePad extends this.OS.GUI.BaseApplication {
|
||||
constructor(args) {
|
||||
super("CodePad", args);
|
||||
this.currfile = "Untitled".asFileHandle();
|
||||
this.currdir = undefined;
|
||||
if (this.args && (this.args.length > 0)) {
|
||||
if (this.args[0].type === "dir") {
|
||||
this.currdir = this.args[0].path.asFileHandle();
|
||||
} else {
|
||||
this.currfile = this.args[0].path.asFileHandle();
|
||||
this.currdir = this.currfile.parent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
this.extensions = {};
|
||||
this.fileview = this.find("fileview");
|
||||
this.sidebar = this.find("sidebar");
|
||||
this.tabbar = this.find("tabbar");
|
||||
this.langstat = this.find("langstat");
|
||||
this.editorstat = this.find("editorstat");
|
||||
|
||||
this.fileview.set("fetch", path => new Promise(function(resolve, reject) {
|
||||
let dir = path;
|
||||
if (typeof path === "string") { dir = path.asFileHandle(); }
|
||||
return dir.read().then(function(d) {
|
||||
if (d.error) { return reject(d.error); }
|
||||
return resolve(d.result);}).catch(e => reject(__e(e)));
|
||||
}));
|
||||
return this.setup();
|
||||
}
|
||||
|
||||
setup() {
|
||||
ace.config.set('basePath', '/scripts/ace');
|
||||
ace.require("ace/ext/language_tools");
|
||||
this.editor = ace.edit(this.find("datarea"));
|
||||
this.editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
enableSnippets: true,
|
||||
enableLiveAutocompletion: true,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectedWord: true,
|
||||
behavioursEnabled: true,
|
||||
wrap: true,
|
||||
fontSize: "11pt",
|
||||
showInvisibles: true
|
||||
});
|
||||
//themes = ace.require "ace/ext/themelist"
|
||||
this.editor.setTheme("ace/theme/monokai");
|
||||
this.modes = ace.require("ace/ext/modelist");
|
||||
this.editor.completers.push({ getCompletions( editor, session, pos, prefix, callback ) {} });
|
||||
this.editor.getSession().setUseWrapMode(true);
|
||||
this.editormux = false;
|
||||
this.editor.on("input", () => {
|
||||
if (this.editormux) {
|
||||
this.editormux = false;
|
||||
return false;
|
||||
}
|
||||
if (!this.currfile.dirty) {
|
||||
this.currfile.dirty = true;
|
||||
this.currfile.text += "*";
|
||||
return this.tabbar.update();
|
||||
}
|
||||
});
|
||||
this.editor.getSession().selection.on("changeCursor", e => {
|
||||
return this.updateStatus();
|
||||
});
|
||||
|
||||
this.tabbar.set("ontabselect", e => {
|
||||
return this.selecteTab($(e.data.item).index());
|
||||
});
|
||||
this.tabbar.set("ontabclose", e => {
|
||||
const it = e.data.item;
|
||||
if (!it) { return false; }
|
||||
if (!it.get("data").dirty) { return this.closeTab(it); }
|
||||
this.openDialog("YesNoDialog", {
|
||||
title: __("Close tab"),
|
||||
text: __("Close without saving ?")
|
||||
}).then(d => {
|
||||
if (d) { return this.closeTab(it); }
|
||||
return this.editor.focus();
|
||||
});
|
||||
return false;
|
||||
});
|
||||
this.fileview.set("onfileopen", e => {
|
||||
if (!e.data || !e.data.path) { return; }
|
||||
if (e.data.type === "dir") { return; }
|
||||
return this.openFile(e.data.path.asFileHandle());
|
||||
});
|
||||
|
||||
this.fileview.set("onfileselect", e => {
|
||||
if (!e.data || !e.data.path) { return; }
|
||||
if (e.data.type === "dir") { return; }
|
||||
const i = this.findTabByFile(e.data.path.asFileHandle());
|
||||
if (i !== -1) { return this.tabbar.set("selected", i); }
|
||||
});
|
||||
|
||||
this.on("resize", () => this.editor.resize());
|
||||
this.on("focus", () => this.editor.focus());
|
||||
this.spotlight = new CMDMenu(__("Command palette"));
|
||||
this.bindKey("ALT-P", () => this.spotlight.run(this));
|
||||
this.find("datarea").contextmenuHandle = (e, m) => {
|
||||
m.set("items", [{
|
||||
text: __("Command palete"),
|
||||
onmenuselect: e => {
|
||||
return this.spotlight.run(this);
|
||||
}
|
||||
}]);
|
||||
return m.show(e);
|
||||
};
|
||||
|
||||
this.fileview.contextmenuHandle = (e, m) => {
|
||||
m.set("items", [
|
||||
{ text: "__(New file)", id: "new" },
|
||||
{ text: "__(New folder)", id: "newdir" },
|
||||
{ text: "__(Rename)", id: "rename" },
|
||||
{ text: "__(Delete)", id: "delete" }
|
||||
]);
|
||||
m.set("onmenuselect", e => {
|
||||
return this.ctxFileMenuHandle(e);
|
||||
});
|
||||
return m.show(e);
|
||||
};
|
||||
|
||||
this.bindKey("ALT-N", () => this.menuAction("new"));
|
||||
this.bindKey("ALT-O", () => this.menuAction("open"));
|
||||
this.bindKey("ALT-F", () => this.menuAction("opendir"));
|
||||
this.bindKey("CTRL-S", () => this.menuAction("save"));
|
||||
this.bindKey("ALT-W", () => this.menuAction("saveas"));
|
||||
|
||||
this.fileview.set("ondragndrop", e => {
|
||||
const src = e.data.from.get("data").path.asFileHandle();
|
||||
const des = e.data.to.get("data").path;
|
||||
return src.move(`${des}/${src.basename}`)
|
||||
.then(function(d) {
|
||||
e.data.to.update(des);
|
||||
return e.data.from.get("parent").update(src.parent().path);}).catch(e => this.error(__("Unable to move file/folder"), e));
|
||||
});
|
||||
|
||||
this.on("filechange", data => {
|
||||
let {
|
||||
path
|
||||
} = data.file;
|
||||
if (data.type === "file") { ({
|
||||
path
|
||||
} = data.file.parent()); }
|
||||
return this.fileview.update(path);
|
||||
});
|
||||
|
||||
|
||||
this.loadExtensionMetaData();
|
||||
this.initCommandPalete();
|
||||
this.initSideBar();
|
||||
return this.openFile(this.currfile);
|
||||
}
|
||||
|
||||
openFile(file) {
|
||||
//find tab
|
||||
const i = this.findTabByFile(file);
|
||||
if (i !== -1) { return this.tabbar.set("selected", i); }
|
||||
if (file.path.toString() === "Untitled") { return this.newTab(file); }
|
||||
|
||||
return file.read()
|
||||
.then(d => {
|
||||
file.cache = d || "";
|
||||
return this.newTab(file);
|
||||
}).catch(e => {
|
||||
return this.error(__("Unable to open: {0}", file.path), e);
|
||||
});
|
||||
}
|
||||
|
||||
findTabByFile(file) {
|
||||
const lst = this.tabbar.get("items");
|
||||
const its = ((() => {
|
||||
const result = [];
|
||||
for (let i = 0; i < lst.length; i++) {
|
||||
const d = lst[i];
|
||||
if (d.hash() === file.hash()) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})());
|
||||
if (its.length === 0) { return -1; }
|
||||
return its[0];
|
||||
}
|
||||
|
||||
newTab(file) {
|
||||
file.text = file.basename ? file.basename : file.path;
|
||||
if (!file.cache) { file.cache = ""; }
|
||||
file.um = new ace.UndoManager();
|
||||
this.currfile.selected = false;
|
||||
file.selected = true;
|
||||
//console.log cnt
|
||||
return this.tabbar.push(file);
|
||||
}
|
||||
|
||||
closeTab(it) {
|
||||
this.tabbar.remove(it);
|
||||
const cnt = this.tabbar.get("items").length;
|
||||
|
||||
if (cnt === 0) {
|
||||
this.openFile("Untitled".asFileHandle());
|
||||
return false;
|
||||
}
|
||||
this.tabbar.set("selected", cnt - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
selecteTab(i) {
|
||||
//return if i is @tabbar.get "selidx"
|
||||
const file = (this.tabbar.get("items"))[i];
|
||||
if (!file) { return; }
|
||||
this.scheme.set("apptitle", file.text.toString());
|
||||
//return if file is @currfile
|
||||
if (this.currfile !== file) {
|
||||
this.currfile.cache = this.editor.getValue();
|
||||
this.currfile.cursor = this.editor.selection.getCursor();
|
||||
this.currfile.selected = false;
|
||||
this.currfile = file;
|
||||
}
|
||||
|
||||
if (!file.langmode) {
|
||||
if (file.path.toString() !== "Untitled") {
|
||||
const m = this.modes.getModeForPath(file.path);
|
||||
file.langmode = { caption: m.caption, mode: m.mode };
|
||||
} else {
|
||||
file.langmode = { caption: "Text", mode: "ace/mode/text" };
|
||||
}
|
||||
}
|
||||
this.editormux = true;
|
||||
this.editor.getSession().setUndoManager(new ace.UndoManager());
|
||||
this.editor.setValue(file.cache, -1);
|
||||
this.editor.getSession().setMode(file.langmode.mode);
|
||||
if (file.cursor) {
|
||||
this.editor.renderer.scrollCursorIntoView({
|
||||
row: file.cursor.row, column: file.cursor.column
|
||||
}, 0.5);
|
||||
this.editor.selection.moveTo(file.cursor.row, file.cursor.column);
|
||||
}
|
||||
this.editor.getSession().setUndoManager(file.um);
|
||||
this.updateStatus();
|
||||
return this.editor.focus();
|
||||
}
|
||||
|
||||
updateStatus() {
|
||||
const c = this.editor.session.selection.getCursor();
|
||||
const l = this.editor.session.getLength();
|
||||
this.editorstat.set("text", __("Row {0}, col {1}, lines: {2}", c.row + 1, c.column + 1, l));
|
||||
return this.langstat.set("text", this.currfile.langmode.caption);
|
||||
}
|
||||
|
||||
initSideBar() {
|
||||
if (this.currdir) {
|
||||
$(this.sidebar).show();
|
||||
this.fileview.set("path", this.currdir.path);
|
||||
} else {
|
||||
$(this.sidebar).hide();
|
||||
}
|
||||
return this.trigger("resize");
|
||||
}
|
||||
|
||||
addAction(action) {
|
||||
this.spotlight.addAction(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
addActions(list) {
|
||||
this.spotlight.addActions(list);
|
||||
return this;
|
||||
}
|
||||
|
||||
initCommandPalete() {
|
||||
let v;
|
||||
const themes = ace.require("ace/ext/themelist");
|
||||
const cmdtheme = new CMDMenu(__("Change theme"));
|
||||
for (let k in themes.themesByName) { v = themes.themesByName[k]; cmdtheme.addAction({ text: v.caption, theme: v.theme }); }
|
||||
cmdtheme.onchildselect(function(d, r) {
|
||||
const data = d.data.item.get("data");
|
||||
r.editor.setTheme(data.theme);
|
||||
return r.editor.focus();
|
||||
});
|
||||
this.spotlight.addAction(cmdtheme);
|
||||
const cmdmode = new CMDMenu(__("Change language mode"));
|
||||
for (v of Array.from(this.modes.modes)) { cmdmode.addAction({ text: v.caption, mode: v.mode }); }
|
||||
cmdmode.onchildselect(function(d, r) {
|
||||
const data = d.data.item.get("data");
|
||||
r.editor.session.setMode(data.mode);
|
||||
r.currfile.langmode = { caption: data.text, mode: data.mode };
|
||||
r.updateStatus();
|
||||
return r.editor.focus();
|
||||
});
|
||||
this.spotlight.addAction(cmdmode);
|
||||
return this.addAction(CMDMenu.fromMenu(this.fileMenu()));
|
||||
}
|
||||
|
||||
loadExtensionMetaData() {
|
||||
return `${this.meta().path}/extensions.json`
|
||||
.asFileHandle()
|
||||
.read("json")
|
||||
.then(d => {
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (var ext of Array.from(d)) {
|
||||
if (this.extensions[ext.name]) {
|
||||
this.extensions[ext.name].child = [];
|
||||
result.push((() => {
|
||||
const result1 = [];
|
||||
for (let v of Array.from(ext.actions)) { result1.push(this.extensions[ext.name].addAction(v));
|
||||
}
|
||||
return result1;
|
||||
})());
|
||||
} else {
|
||||
this.extensions[ext.name] = new CMDMenu(ext.text);
|
||||
this.extensions[ext.name].name = ext.name;
|
||||
for (let v of Array.from(ext.actions)) { this.extensions[ext.name].addAction(v); }
|
||||
this.spotlight.addAction(this.extensions[ext.name]);
|
||||
result.push(this.extensions[ext.name].onchildselect(e => {
|
||||
return this.loadAndRunExtensionAction(e.data.item.get("data"));
|
||||
}));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}).catch(e => {
|
||||
return this.error(__("Cannot load extension meta data"), e);
|
||||
});
|
||||
}
|
||||
|
||||
runExtensionAction(name, action) {
|
||||
if (!CodePad.extensions[name]) { return this.error(__("Unable to find extension: {0}", name)); }
|
||||
const ext = new (CodePad.extensions[name])(this);
|
||||
if (!ext[action]) { return this.error(__("Unable to find action: {0}", action)); }
|
||||
return ext.preload()
|
||||
.then(() => ext[action]()).catch(e => {
|
||||
return this.error(__("Unable to preload extension"), e);
|
||||
});
|
||||
}
|
||||
|
||||
loadAndRunExtensionAction(data) {
|
||||
const {
|
||||
name
|
||||
} = data.parent;
|
||||
const action = data.name;
|
||||
//verify if the extension is load
|
||||
if (!CodePad.extensions[name]) {
|
||||
//load the extension
|
||||
const path = `${this.meta().path}/extensions/${name}.js`;
|
||||
return this._api.requires(path)
|
||||
.then(() => this.runExtensionAction(name, action))
|
||||
.catch(e => {
|
||||
return this.error(__("unable to load extension: {0}", name), e);
|
||||
});
|
||||
} else {
|
||||
return this.runExtensionAction(name, action);
|
||||
}
|
||||
}
|
||||
|
||||
fileMenu() {
|
||||
return {
|
||||
text: __("File"),
|
||||
child: [
|
||||
{ text: __("New"), dataid: "new", shortcut: "A-N" },
|
||||
{ text: __("Open"), dataid: "open", shortcut: "A-O" },
|
||||
{ text: __("Open Folder"), dataid: "opendir", shortcut: "A-F" },
|
||||
{ text: __("Save"), dataid: "save", shortcut: "C-S" },
|
||||
{ text: __("Save as"), dataid: "saveas", shortcut: "A-W" }
|
||||
],
|
||||
onchildselect: (e, r) => {
|
||||
return this.menuAction(e.data.item.get("data").dataid, r);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ctxFileMenuHandle(e) {
|
||||
const el = e.data.item;
|
||||
if (!el) { return; }
|
||||
const data = el.get("data");
|
||||
if (!data) { return; }
|
||||
let file = this.fileview.get("selectedFile");
|
||||
let dir = this.currdir;
|
||||
if (file && (file.type === "dir")) { dir = file.path.asFileHandle(); }
|
||||
if (file && (file.type === "file")) { dir = file.path.asFileHandle().parent(); }
|
||||
|
||||
switch (data.id) {
|
||||
case "new":
|
||||
if (!dir) { return; }
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: "__(New file)",
|
||||
label: "__(File name)"
|
||||
})
|
||||
.then(d => {
|
||||
const fp = `${dir.path}/${d}`.asFileHandle();
|
||||
return fp.write("text/plain")
|
||||
.then(r => {
|
||||
return this.fileview.update(dir.path);
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to create: {0}", e.stack), e);
|
||||
});
|
||||
});
|
||||
|
||||
case "newdir":
|
||||
if (!dir) { return; }
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: "__(New folder)",
|
||||
label: "__(Folder name)"
|
||||
})
|
||||
.then(d => {
|
||||
return dir.mk(d)
|
||||
.then(r => {
|
||||
return this.fileview.update(dir.path);
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to create: {0}", dir.path), e);
|
||||
});
|
||||
});
|
||||
|
||||
case "rename":
|
||||
if (!file) { return; }
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: "__(Rename)",
|
||||
label: "__(File name)",
|
||||
value: file.filename
|
||||
})
|
||||
.then(d => {
|
||||
if (d === file.filename) { return; }
|
||||
file = file.path.asFileHandle();
|
||||
dir = file.parent();
|
||||
return file.move(`${dir.path}/${d}`)
|
||||
.then(r => {
|
||||
return this.fileview.update(dir.path);
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to rename: {0}", file.path), e);
|
||||
});
|
||||
});
|
||||
|
||||
case "delete":
|
||||
if (!file) { return; }
|
||||
return this.openDialog("YesNoDialog", {
|
||||
title: "__(Delete)",
|
||||
iconclass: "fa fa-question-circle",
|
||||
text: __("Do you really want to delete: {0}?", file.filename)
|
||||
})
|
||||
.then(d => {
|
||||
if (!d) { return; }
|
||||
file = file.path.asFileHandle();
|
||||
dir = file.parent();
|
||||
return file.remove()
|
||||
.then(r => {
|
||||
return this.fileview.update(dir.path);
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to delete: {0}", file.path), e);
|
||||
});
|
||||
});
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
save(file) {
|
||||
return file.write("text/plain")
|
||||
.then(d => {
|
||||
file.dirty = false;
|
||||
file.text = file.basename;
|
||||
this.tabbar.update();
|
||||
return this.scheme.set("apptitle", `${this.currfile.basename}`);
|
||||
}).catch(e => this.error(__("Unable to save file: {0}", file.path), e));
|
||||
}
|
||||
|
||||
|
||||
saveAs() {
|
||||
return this.openDialog("FileDialog", {
|
||||
title: __("Save as"),
|
||||
file: this.currfile
|
||||
})
|
||||
.then(f => {
|
||||
let d = f.file.path.asFileHandle();
|
||||
if (f.file.type === "file") { d = d.parent(); }
|
||||
this.currfile.setPath(`${d.path}/${f.name}`);
|
||||
return this.save(this.currfile);
|
||||
});
|
||||
}
|
||||
|
||||
menuAction(dataid, r) {
|
||||
let me = this;
|
||||
if (r) { me = r; }
|
||||
switch (dataid) {
|
||||
case "new":
|
||||
return me.openFile("Untitled".asFileHandle());
|
||||
case "open":
|
||||
return me.openDialog("FileDialog", {
|
||||
title: __("Open file"),
|
||||
mimes: (Array.from(me.meta().mimes).filter((v) => v !== "dir"))
|
||||
})
|
||||
.then(f => me.openFile(f.file.path.asFileHandle()));
|
||||
case "opendir":
|
||||
return me.openDialog("FileDialog", {
|
||||
title: __("Open folder"),
|
||||
mimes: ["dir"]
|
||||
})
|
||||
.then(function(f) {
|
||||
me.currdir = f.file.path.asFileHandle();
|
||||
return me.initSideBar();
|
||||
});
|
||||
case "save":
|
||||
me.currfile.cache = me.editor.getValue();
|
||||
if (me.currfile.basename) { return me.save(me.currfile); }
|
||||
return me.saveAs();
|
||||
case "saveas":
|
||||
me.currfile.cache = me.editor.getValue();
|
||||
return me.saveAs();
|
||||
default:
|
||||
return console.log(dataid);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup(evt) {
|
||||
let v;
|
||||
const dirties = ((() => {
|
||||
const result = [];
|
||||
for (v of Array.from(this.tabbar.get("items"))) { if (v.dirty) {
|
||||
result.push(v);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})());
|
||||
if (dirties.length === 0) { return; }
|
||||
evt.preventDefault();
|
||||
return this.openDialog("YesNoDialog", {
|
||||
title: "__(Quit)",
|
||||
text: __("Ignore all unsaved files: {0} ?", ((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(dirties)) { result1.push(v.filename());
|
||||
}
|
||||
return result1;
|
||||
})()).join(", ") )
|
||||
}).then(d => {
|
||||
if (d) {
|
||||
for (v of Array.from(dirties)) { v.dirty = false; }
|
||||
return this.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
menu() {
|
||||
const menu = [
|
||||
this.fileMenu(),
|
||||
{
|
||||
text: "__(View)",
|
||||
child: [
|
||||
{ text: "__(Command Palette)", dataid: "cmdpalette", shortcut: "A-P" }
|
||||
],
|
||||
onchildselect: (e, r) => {
|
||||
return this.spotlight.run(this);
|
||||
}
|
||||
}
|
||||
];
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
|
||||
class CMDMenu {
|
||||
constructor(text, shortcut) {
|
||||
this.text = text;
|
||||
this.shortcut = shortcut;
|
||||
this.child = [];
|
||||
this.parent = undefined;
|
||||
this.select = function(e) {};
|
||||
}
|
||||
|
||||
addAction(v) {
|
||||
v.parent = this;
|
||||
this.child.push(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
addActions(list) {
|
||||
return Array.from(list).map((v) => this.addAction(v));
|
||||
}
|
||||
|
||||
onchildselect(f) {
|
||||
this.select = f;
|
||||
return this;
|
||||
}
|
||||
|
||||
run(root) {
|
||||
return root.openDialog(new CommandPalette(), this)
|
||||
.then(d => {
|
||||
const data = d.data.item.get("data");
|
||||
if (data.run) { return data.run(root); }
|
||||
return this.select(d, root);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CMDMenu.fromMenu = function(mn) {
|
||||
const m = new CMDMenu(mn.text, mn.shortcut);
|
||||
m.onchildselect(mn.onchildselect);
|
||||
for (let v of Array.from(mn.child)) {
|
||||
if (v.child) {
|
||||
m.addAction(CMDMenu.fromMenu(v));
|
||||
} else {
|
||||
m.addAction(v);
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
CodePad.CMDMenu = CMDMenu;
|
||||
|
||||
CodePad.dependencies = [
|
||||
"os://scripts/ace/ace.js",
|
||||
"os://scripts/ace/ext-language_tools.js",
|
||||
"os://scripts/ace/ext-modelist.js",
|
||||
"os://scripts/ace/ext-themelist.js"
|
||||
];
|
||||
this.OS.register("CodePad", CodePad);
|
@ -1,6 +1,6 @@
|
||||
coffee_files = main.coffee
|
||||
module_files = main.js
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
|
@ -1,363 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class Files extends this.OS.GUI.BaseApplication
|
||||
constructor: (args) ->
|
||||
super "Files", args
|
||||
|
||||
main: () ->
|
||||
@scheme.set "apptitle", "Files manager"
|
||||
@view = @find "fileview"
|
||||
@navinput = @find "navinput"
|
||||
@navbar = @find "nav-bar"
|
||||
if @args and @args.length > 0
|
||||
@currdir = @args[0].path.asFileHandle()
|
||||
else
|
||||
@currdir = "home://".asFileHandle()
|
||||
@favo = @find "favouri"
|
||||
@clipboard = undefined
|
||||
@viewType = @_api.switcher "icon", "list", "tree"
|
||||
@viewType.list = true
|
||||
|
||||
@view.contextmenuHandle = (e, m) =>
|
||||
file = @view.get "selectedFile"
|
||||
return unless file
|
||||
apps = []
|
||||
file.mime = "dir" if file.type is "dir"
|
||||
|
||||
for v in @_gui.appsByMime file.mime
|
||||
apps.push {
|
||||
text: v.text,
|
||||
app: v.app,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass
|
||||
}
|
||||
|
||||
m.set "items", [
|
||||
{
|
||||
text: "__(Open with)",
|
||||
child: apps,
|
||||
onchildselect: (e) =>
|
||||
return unless e
|
||||
it = e.data.item.get("data")
|
||||
@_gui.launch it.app, [file]
|
||||
},
|
||||
@mnFile(),
|
||||
@mnEdit()
|
||||
]
|
||||
m.show e
|
||||
|
||||
@view.set "onfileopen", (e) =>
|
||||
return unless e.data
|
||||
return if e.data.type is "dir"
|
||||
@_gui.openWith e.data
|
||||
|
||||
@favo.set "onlistselect", (e) =>
|
||||
@view.set "path", e.data.item.get("data").path
|
||||
|
||||
($ @find "btback").click () =>
|
||||
return if @currdir.isRoot()
|
||||
p = @currdir.parent()
|
||||
@favo.set "selected", -1
|
||||
@view.set "path", p.path
|
||||
|
||||
($ @navinput).keyup (e) =>
|
||||
@view.set "path", ($ @navinput).val() if e.keyCode is 13 #enter
|
||||
|
||||
@view.set "fetch", (path) =>
|
||||
new Promise (resolve, reject) =>
|
||||
dir = path
|
||||
dir = path.asFileHandle() if typeof path is "string"
|
||||
dir.read().then (d) =>
|
||||
return reject d.error if d.error
|
||||
if not dir.isRoot()
|
||||
p = dir.parent()
|
||||
p.filename = "[..]"
|
||||
p.type = "dir"
|
||||
d.result.unshift p
|
||||
@currdir = dir
|
||||
($ @navinput).val dir.path
|
||||
resolve d.result
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
@vfs_event_flag = true
|
||||
@view.set "ondragndrop", (e) =>
|
||||
return unless e
|
||||
src = e.data.from.get("data")
|
||||
des = e.data.to.get("data")
|
||||
return if des.type is "file"
|
||||
file = src.path.asFileHandle()
|
||||
# disable the vfs event on
|
||||
# we update it manually
|
||||
@vfs_event_flag = false
|
||||
file.move "#{des.path}/#{file.basename}"
|
||||
.then () =>
|
||||
if @view.get("view") is "icon"
|
||||
@view.set "path", @view.get("path")
|
||||
else
|
||||
@view.update file.parent().path
|
||||
@view.update des.path
|
||||
#reenable the vfs event
|
||||
@vfs_event_flag = true
|
||||
.catch (e) =>
|
||||
# reenable the vfs event
|
||||
@vfs_event_flag = true
|
||||
@error __("Unable to move: {0} -> {1}", src.path, des.path), e
|
||||
|
||||
# application setting
|
||||
@setting.sidebar = true if @setting.sidebar is undefined
|
||||
@setting.nav = true if @setting.nav is undefined
|
||||
@setting.showhidden = false if @setting.showhidden is undefined
|
||||
@applyAllSetting()
|
||||
|
||||
# VFS mount point and event
|
||||
mntpoints = @systemsetting.VFS.mountpoints
|
||||
el.selected = false for el, i in mntpoints
|
||||
@favo.set "data", mntpoints
|
||||
#@favo.set "selected", -1
|
||||
@view.set "view", @setting.view if @setting.view
|
||||
@subscribe "VFS", (d) =>
|
||||
return unless @vfs_event_flag
|
||||
return if ["read", "publish", "download"].includes d.data.m
|
||||
if (d.data.file.hash() is @currdir.hash() or
|
||||
d.data.file.parent().hash() is @currdir.hash())
|
||||
@view.set "path", @currdir
|
||||
|
||||
# bind keyboard shortcut
|
||||
@bindKey "CTRL-F", () => @actionFile "#{@name}-mkf"
|
||||
@bindKey "CTRL-D", () => @actionFile "#{@name}-mkdir"
|
||||
@bindKey "CTRL-U", () => @actionFile "#{@name}-upload"
|
||||
@bindKey "CTRL-S", () => @actionFile "#{@name}-share"
|
||||
@bindKey "CTRL-I", () => @actionFile "#{@name}-info"
|
||||
|
||||
@bindKey "CTRL-R", () => @actionEdit "#{@name}-mv"
|
||||
@bindKey "CTRL-M", () => @actionEdit "#{@name}-rm"
|
||||
@bindKey "CTRL-X", () => @actionEdit "#{@name}-cut"
|
||||
@bindKey "CTRL-C", () => @actionEdit "#{@name}-copy"
|
||||
@bindKey "CTRL-P", () => @actionEdit "#{@name}-paste"
|
||||
|
||||
(@find "btgrid").set "onbtclick", (e) =>
|
||||
@view.set 'view', "icon"
|
||||
@viewType.icon = true
|
||||
|
||||
(@find "btlist").set "onbtclick", (e) =>
|
||||
@view.set 'view', "list"
|
||||
@viewType.list = true
|
||||
@view.set "path", @currdir
|
||||
|
||||
applySetting: (k) ->
|
||||
# view setting
|
||||
switch k
|
||||
when "showhidden" then @view.set "showhidden", @setting.showhidden
|
||||
when "nav" then @toggleNav @setting.nav
|
||||
when "sidebar" then @toggleSidebar @setting.sidebar
|
||||
|
||||
mnFile: () ->
|
||||
#console.log file
|
||||
arr = {
|
||||
text: "__(File)",
|
||||
child: [
|
||||
{ text: "__(New file)", dataid: "#{@name}-mkf", shortcut: 'C-F' },
|
||||
{ text: "__(New folder)", dataid: "#{@name}-mkdir", shortcut: 'C-D' },
|
||||
{ text: "__(Upload)", dataid: "#{@name}-upload", shortcut: 'C-U' },
|
||||
{ text: "__(Download)", dataid: "#{@name}-download" },
|
||||
{ text: "__(Share file)", dataid: "#{@name}-share", shortcut: 'C-S' },
|
||||
{ text: "__(Properties)", dataid: "#{@name}-info", shortcut: 'C-I' }
|
||||
], onchildselect: (e) => @actionFile e.data.item.get("data").dataid
|
||||
}
|
||||
return arr
|
||||
mnEdit: () ->
|
||||
{
|
||||
text: "__(Edit)",
|
||||
child: [
|
||||
{ text: "__(Rename)", dataid: "#{@name}-mv", shortcut: 'C-R' },
|
||||
{ text: "__(Delete)", dataid: "#{@name}-rm", shortcut: 'C-M' },
|
||||
{ text: "__(Cut)", dataid: "#{@name}-cut", shortcut: 'C-X' },
|
||||
{ text: "__(Copy)", dataid: "#{@name}-copy", shortcut: 'C-C' },
|
||||
{ text: "__(Paste)", dataid: "#{@name}-paste", shortcut: 'C-P' }
|
||||
], onchildselect: (e) => @actionEdit e.data.item.get("data").dataid
|
||||
}
|
||||
menu: () ->
|
||||
|
||||
menu = [
|
||||
@mnFile(),
|
||||
@mnEdit(),
|
||||
{
|
||||
text: "__(View)",
|
||||
child: [
|
||||
{ text: "__(Refresh)", dataid: "#{@name}-refresh" },
|
||||
{ text: "__(Sidebar)", switch: true, checked: @setting.sidebar, dataid: "#{@name}-side" },
|
||||
{ text: "__(Navigation bar)", switch: true, checked: @setting.nav, dataid: "#{@name}-nav" },
|
||||
{ text: "__(Hidden files)", switch: true, checked: @setting.showhidden, dataid: "#{@name}-hidden" },
|
||||
{ text: "__(Type)", child: [
|
||||
{ text: "__(Icon view)", radio: true, checked: @viewType.icon, dataid: "#{@name}-icon", type: 'icon' },
|
||||
{ text: "__(List view)", radio:true, checked: @viewType.list, dataid: "#{@name}-list", type: 'list' },
|
||||
{ text: "__(Tree view)", radio:true, checked: @viewType.tree, dataid: "#{@name}-tree", type: 'tree' }
|
||||
], onchildselect: (e) =>
|
||||
type = e.data.item.get("data").type
|
||||
@view.set 'view', type
|
||||
@viewType[type] = true
|
||||
},
|
||||
], onchildselect: (e) => @actionView e
|
||||
},
|
||||
]
|
||||
menu
|
||||
|
||||
toggleSidebar: (b) ->
|
||||
if b then ($ @favo).show() else ($ @favo).hide()
|
||||
@trigger "resize"
|
||||
|
||||
toggleNav: (b) ->
|
||||
if b then ($ @navbar).show() else ($ @navbar).hide()
|
||||
@trigger "resize"
|
||||
|
||||
actionView: (e) ->
|
||||
data = e.data.item.get("data")
|
||||
switch data.dataid
|
||||
when "#{@name}-hidden"
|
||||
#@.view.set "showhidden", e.item.data.checked
|
||||
@registry "showhidden", data.checked
|
||||
#@.setting.showhidden = e.item.data.checked
|
||||
when "#{@name}-refresh"
|
||||
@.chdir null
|
||||
when "#{@name}-side"
|
||||
@registry "sidebar", data.checked
|
||||
#@setting.sidebar = e.item.data.checked
|
||||
#@toggleSidebar e.item.data.checked
|
||||
when "#{@name}-nav"
|
||||
@registry "nav", data.checked
|
||||
#@setting.nav = e.item.data.checked
|
||||
#@toggleNav e.item.data.checked
|
||||
|
||||
actionEdit: (e) ->
|
||||
file = @view.get "selectedFile"
|
||||
switch e
|
||||
when "#{@name}-mv"
|
||||
return unless file
|
||||
@openDialog("PromptDialog", {
|
||||
title: "__(Rename)",
|
||||
label: "__(File name)",
|
||||
value: file.filename
|
||||
})
|
||||
.then (d) =>
|
||||
return if d is file.filename
|
||||
file.path.asFileHandle().move "#{@currdir.path}/#{d}"
|
||||
.catch (e) =>
|
||||
@error __("Fail to rename: {0}", file.path), e
|
||||
|
||||
when "#{@name}-rm"
|
||||
return unless file
|
||||
@openDialog("YesNoDialog", {
|
||||
title: "__(Delete)",
|
||||
iconclass: "fa fa-question-circle",
|
||||
text: __("Do you really want to delete: {0}?", file.filename)
|
||||
})
|
||||
.then (d) =>
|
||||
return unless d
|
||||
file.path.asFileHandle().remove()
|
||||
.catch (e) =>
|
||||
@error __("Fail to delete: {0}", file.path), e
|
||||
|
||||
when "#{@name}-cut"
|
||||
return unless file
|
||||
@clipboard =
|
||||
cut: true
|
||||
file: file.path.asFileHandle()
|
||||
@notify __("File {0} cut", file.filename)
|
||||
|
||||
when "#{@name}-copy"
|
||||
return unless file or file.type is "dir"
|
||||
@clipboard =
|
||||
cut: false
|
||||
file: file.path.asFileHandle()
|
||||
@notify __("File {0} copied", file.filename)
|
||||
|
||||
when "#{@name}-paste"
|
||||
return unless @clipboard
|
||||
if @clipboard.cut
|
||||
@clipboard.file.move "#{@currdir.path}/#{@clipboard.file.basename}"
|
||||
.then (r) =>
|
||||
@clipboard = undefined
|
||||
.catch (e) =>
|
||||
@error __("Fail to paste: {0}", @clipboard.file.path), e
|
||||
else
|
||||
@clipboard.file.read("binary")
|
||||
.then (d) =>
|
||||
blob = new Blob [d], { type: @clipboard.file.info.mime }
|
||||
fp = "#{@currdir.path}/#{@clipboard.file.basename}".asFileHandle()
|
||||
fp.cache = blob
|
||||
fp.write(@clipboard.file.info.mime)
|
||||
.then (r) =>
|
||||
@clipboard = undefined
|
||||
.catch (e) =>
|
||||
@error __("Fail to paste: {0}", @clipboard.file.path), e
|
||||
.catch (e) =>
|
||||
@error __("Fail to read: {0}", @clipboard.file.path), e
|
||||
else
|
||||
@_api.handle.setting()
|
||||
|
||||
actionFile: (e) ->
|
||||
file = @view.get "selectedFile"
|
||||
switch e
|
||||
when "#{@name}-mkdir"
|
||||
@openDialog("PromptDialog", {
|
||||
title: "__(New folder)",
|
||||
label: "__(Folder name)"
|
||||
})
|
||||
.then (d) =>
|
||||
@currdir.mk(d)
|
||||
.catch (e) =>
|
||||
@error __("Fail to create: {0}", d), e
|
||||
|
||||
when "#{@name}-mkf"
|
||||
@openDialog("PromptDialog", {
|
||||
title: "__(New file)",
|
||||
label: "__(File name)"
|
||||
})
|
||||
.then (d) =>
|
||||
fp = "#{@currdir.path}/#{d}".asFileHandle()
|
||||
fp.write("text/plain")
|
||||
.catch (e) =>
|
||||
@error __("Fail to create: {0}", fp.path)
|
||||
|
||||
when "#{@name}-info"
|
||||
return unless file
|
||||
@openDialog "InfoDialog", file
|
||||
|
||||
when "#{@name}-upload"
|
||||
@currdir.upload()
|
||||
.catch (e) =>
|
||||
@error __("Fail to upload: {0}", e.toString()), e
|
||||
|
||||
when "#{@name}-share"
|
||||
return unless file and file.type is "file"
|
||||
file.path.asFileHandle().publish()
|
||||
.then (r) =>
|
||||
@notify __("Shared url: {0}", r.result)
|
||||
.catch (e) =>
|
||||
@error __("Fail to publish: {0}", file.path), e
|
||||
|
||||
when "#{@name}-download"
|
||||
return unless file.type is "file"
|
||||
file.path.asFileHandle().download()
|
||||
.catch (e) =>
|
||||
@error __("Fail to download: {0}", file.path), e
|
||||
else
|
||||
console.log e
|
||||
|
||||
this.OS.register "Files", Files
|
424
src/packages/Files/main.js
Normal file
424
src/packages/Files/main.js
Normal file
@ -0,0 +1,424 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class Files extends this.OS.GUI.BaseApplication {
|
||||
constructor(args) {
|
||||
super("Files", args);
|
||||
}
|
||||
|
||||
main() {
|
||||
this.scheme.set("apptitle", "Files manager");
|
||||
this.view = this.find("fileview");
|
||||
this.navinput = this.find("navinput");
|
||||
this.navbar = this.find("nav-bar");
|
||||
if (this.args && (this.args.length > 0)) {
|
||||
this.currdir = this.args[0].path.asFileHandle();
|
||||
} else {
|
||||
this.currdir = "home://".asFileHandle();
|
||||
}
|
||||
this.favo = this.find("favouri");
|
||||
this.clipboard = undefined;
|
||||
this.viewType = this._api.switcher("icon", "list", "tree");
|
||||
this.viewType.list = true;
|
||||
|
||||
this.view.contextmenuHandle = (e, m) => {
|
||||
const file = this.view.get("selectedFile");
|
||||
if (!file) { return; }
|
||||
const apps = [];
|
||||
if (file.type === "dir") { file.mime = "dir"; }
|
||||
|
||||
for (let v of Array.from(this._gui.appsByMime(file.mime))) {
|
||||
apps.push({
|
||||
text: v.text,
|
||||
app: v.app,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass
|
||||
});
|
||||
}
|
||||
|
||||
m.set("items", [
|
||||
{
|
||||
text: "__(Open with)",
|
||||
child: apps,
|
||||
onchildselect: e => {
|
||||
if (!e) { return; }
|
||||
const it = e.data.item.get("data");
|
||||
return this._gui.launch(it.app, [file]);
|
||||
}
|
||||
},
|
||||
this.mnFile(),
|
||||
this.mnEdit()
|
||||
]);
|
||||
return m.show(e);
|
||||
};
|
||||
|
||||
this.view.set("onfileopen", e => {
|
||||
if (!e.data) { return; }
|
||||
if (e.data.type === "dir") { return; }
|
||||
return this._gui.openWith(e.data);
|
||||
});
|
||||
|
||||
this.favo.set("onlistselect", e => {
|
||||
return this.view.set("path", e.data.item.get("data").path);
|
||||
});
|
||||
|
||||
($(this.find("btback"))).click(() => {
|
||||
if (this.currdir.isRoot()) { return; }
|
||||
const p = this.currdir.parent();
|
||||
this.favo.set("selected", -1);
|
||||
return this.view.set("path", p.path);
|
||||
});
|
||||
|
||||
($(this.navinput)).keyup(e => {
|
||||
if (e.keyCode === 13) { return this.view.set("path", ($(this.navinput)).val()); }
|
||||
}); //enter
|
||||
|
||||
this.view.set("fetch", path => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let dir = path;
|
||||
if (typeof path === "string") { dir = path.asFileHandle(); }
|
||||
return dir.read().then(d => {
|
||||
if (d.error) { return reject(d.error); }
|
||||
if (!dir.isRoot()) {
|
||||
const p = dir.parent();
|
||||
p.filename = "[..]";
|
||||
p.type = "dir";
|
||||
d.result.unshift(p);
|
||||
}
|
||||
this.currdir = dir;
|
||||
($(this.navinput)).val(dir.path);
|
||||
return resolve(d.result);
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
});
|
||||
|
||||
this.vfs_event_flag = true;
|
||||
this.view.set("ondragndrop", e => {
|
||||
if (!e) { return; }
|
||||
const src = e.data.from.get("data");
|
||||
const des = e.data.to.get("data");
|
||||
if (des.type === "file") { return; }
|
||||
const file = src.path.asFileHandle();
|
||||
// disable the vfs event on
|
||||
// we update it manually
|
||||
this.vfs_event_flag = false;
|
||||
return file.move(`${des.path}/${file.basename}`)
|
||||
.then(() => {
|
||||
if (this.view.get("view") === "icon") {
|
||||
this.view.set("path", this.view.get("path"));
|
||||
} else {
|
||||
this.view.update(file.parent().path);
|
||||
this.view.update(des.path);
|
||||
}
|
||||
//reenable the vfs event
|
||||
return this.vfs_event_flag = true;
|
||||
}).catch(e => {
|
||||
// reenable the vfs event
|
||||
this.vfs_event_flag = true;
|
||||
return this.error(__("Unable to move: {0} -> {1}", src.path, des.path), e);
|
||||
});
|
||||
});
|
||||
|
||||
// application setting
|
||||
if (this.setting.sidebar === undefined) { this.setting.sidebar = true; }
|
||||
if (this.setting.nav === undefined) { this.setting.nav = true; }
|
||||
if (this.setting.showhidden === undefined) { this.setting.showhidden = false; }
|
||||
this.applyAllSetting();
|
||||
|
||||
// VFS mount point and event
|
||||
const mntpoints = this.systemsetting.VFS.mountpoints;
|
||||
for (let i = 0; i < mntpoints.length; i++) { const el = mntpoints[i]; el.selected = false; }
|
||||
this.favo.set("data", mntpoints);
|
||||
//@favo.set "selected", -1
|
||||
if (this.setting.view) { this.view.set("view", this.setting.view); }
|
||||
this.subscribe("VFS", d => {
|
||||
if (!this.vfs_event_flag) { return; }
|
||||
if (["read", "publish", "download"].includes(d.data.m)) { return; }
|
||||
if ((d.data.file.hash() === this.currdir.hash()) ||
|
||||
(d.data.file.parent().hash() === this.currdir.hash())) {
|
||||
return this.view.set("path", this.currdir);
|
||||
}
|
||||
});
|
||||
|
||||
// bind keyboard shortcut
|
||||
this.bindKey("CTRL-F", () => this.actionFile(`${this.name}-mkf`));
|
||||
this.bindKey("CTRL-D", () => this.actionFile(`${this.name}-mkdir`));
|
||||
this.bindKey("CTRL-U", () => this.actionFile(`${this.name}-upload`));
|
||||
this.bindKey("CTRL-S", () => this.actionFile(`${this.name}-share`));
|
||||
this.bindKey("CTRL-I", () => this.actionFile(`${this.name}-info`));
|
||||
|
||||
this.bindKey("CTRL-R", () => this.actionEdit(`${this.name}-mv`));
|
||||
this.bindKey("CTRL-M", () => this.actionEdit(`${this.name}-rm`));
|
||||
this.bindKey("CTRL-X", () => this.actionEdit(`${this.name}-cut`));
|
||||
this.bindKey("CTRL-C", () => this.actionEdit(`${this.name}-copy`));
|
||||
this.bindKey("CTRL-P", () => this.actionEdit(`${this.name}-paste`));
|
||||
|
||||
(this.find("btgrid")).set("onbtclick", e => {
|
||||
this.view.set('view', "icon");
|
||||
return this.viewType.icon = true;
|
||||
});
|
||||
|
||||
(this.find("btlist")).set("onbtclick", e => {
|
||||
this.view.set('view', "list");
|
||||
return this.viewType.list = true;
|
||||
});
|
||||
return this.view.set("path", this.currdir);
|
||||
}
|
||||
|
||||
applySetting(k) {
|
||||
// view setting
|
||||
switch (k) {
|
||||
case "showhidden": return this.view.set("showhidden", this.setting.showhidden);
|
||||
case "nav": return this.toggleNav(this.setting.nav);
|
||||
case "sidebar": return this.toggleSidebar(this.setting.sidebar);
|
||||
}
|
||||
}
|
||||
|
||||
mnFile() {
|
||||
//console.log file
|
||||
const arr = {
|
||||
text: "__(File)",
|
||||
child: [
|
||||
{ text: "__(New file)", dataid: `${this.name}-mkf`, shortcut: 'C-F' },
|
||||
{ text: "__(New folder)", dataid: `${this.name}-mkdir`, shortcut: 'C-D' },
|
||||
{ text: "__(Upload)", dataid: `${this.name}-upload`, shortcut: 'C-U' },
|
||||
{ text: "__(Download)", dataid: `${this.name}-download` },
|
||||
{ text: "__(Share file)", dataid: `${this.name}-share`, shortcut: 'C-S' },
|
||||
{ text: "__(Properties)", dataid: `${this.name}-info`, shortcut: 'C-I' }
|
||||
], onchildselect: e => this.actionFile(e.data.item.get("data").dataid)
|
||||
};
|
||||
return arr;
|
||||
}
|
||||
mnEdit() {
|
||||
return {
|
||||
text: "__(Edit)",
|
||||
child: [
|
||||
{ text: "__(Rename)", dataid: `${this.name}-mv`, shortcut: 'C-R' },
|
||||
{ text: "__(Delete)", dataid: `${this.name}-rm`, shortcut: 'C-M' },
|
||||
{ text: "__(Cut)", dataid: `${this.name}-cut`, shortcut: 'C-X' },
|
||||
{ text: "__(Copy)", dataid: `${this.name}-copy`, shortcut: 'C-C' },
|
||||
{ text: "__(Paste)", dataid: `${this.name}-paste`, shortcut: 'C-P' }
|
||||
], onchildselect: e => this.actionEdit(e.data.item.get("data").dataid)
|
||||
};
|
||||
}
|
||||
menu() {
|
||||
|
||||
const menu = [
|
||||
this.mnFile(),
|
||||
this.mnEdit(),
|
||||
{
|
||||
text: "__(View)",
|
||||
child: [
|
||||
{ text: "__(Refresh)", dataid: `${this.name}-refresh` },
|
||||
{ text: "__(Sidebar)", switch: true, checked: this.setting.sidebar, dataid: `${this.name}-side` },
|
||||
{ text: "__(Navigation bar)", switch: true, checked: this.setting.nav, dataid: `${this.name}-nav` },
|
||||
{ text: "__(Hidden files)", switch: true, checked: this.setting.showhidden, dataid: `${this.name}-hidden` },
|
||||
{ text: "__(Type)", child: [
|
||||
{ text: "__(Icon view)", radio: true, checked: this.viewType.icon, dataid: `${this.name}-icon`, type: 'icon' },
|
||||
{ text: "__(List view)", radio:true, checked: this.viewType.list, dataid: `${this.name}-list`, type: 'list' },
|
||||
{ text: "__(Tree view)", radio:true, checked: this.viewType.tree, dataid: `${this.name}-tree`, type: 'tree' }
|
||||
], onchildselect: e => {
|
||||
const {
|
||||
type
|
||||
} = e.data.item.get("data");
|
||||
this.view.set('view', type);
|
||||
return this.viewType[type] = true;
|
||||
}
|
||||
},
|
||||
], onchildselect: e => this.actionView(e)
|
||||
},
|
||||
];
|
||||
return menu;
|
||||
}
|
||||
|
||||
toggleSidebar(b) {
|
||||
if (b) { ($(this.favo)).show(); } else { ($(this.favo)).hide(); }
|
||||
return this.trigger("resize");
|
||||
}
|
||||
|
||||
toggleNav(b) {
|
||||
if (b) { ($(this.navbar)).show(); } else { ($(this.navbar)).hide(); }
|
||||
return this.trigger("resize");
|
||||
}
|
||||
|
||||
actionView(e) {
|
||||
const data = e.data.item.get("data");
|
||||
switch (data.dataid) {
|
||||
case `${this.name}-hidden`:
|
||||
//@.view.set "showhidden", e.item.data.checked
|
||||
return this.registry("showhidden", data.checked);
|
||||
//@.setting.showhidden = e.item.data.checked
|
||||
case `${this.name}-refresh`:
|
||||
return this.chdir(null);
|
||||
case `${this.name}-side`:
|
||||
return this.registry("sidebar", data.checked);
|
||||
//@setting.sidebar = e.item.data.checked
|
||||
//@toggleSidebar e.item.data.checked
|
||||
case `${this.name}-nav`:
|
||||
return this.registry("nav", data.checked);
|
||||
}
|
||||
}
|
||||
//@setting.nav = e.item.data.checked
|
||||
//@toggleNav e.item.data.checked
|
||||
|
||||
actionEdit(e) {
|
||||
const file = this.view.get("selectedFile");
|
||||
switch (e) {
|
||||
case `${this.name}-mv`:
|
||||
if (!file) { return; }
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: "__(Rename)",
|
||||
label: "__(File name)",
|
||||
value: file.filename
|
||||
})
|
||||
.then(d => {
|
||||
if (d === file.filename) { return; }
|
||||
return file.path.asFileHandle().move(`${this.currdir.path}/${d}`)
|
||||
.catch(e => {
|
||||
return this.error(__("Fail to rename: {0}", file.path), e);
|
||||
});
|
||||
});
|
||||
|
||||
case `${this.name}-rm`:
|
||||
if (!file) { return; }
|
||||
return this.openDialog("YesNoDialog", {
|
||||
title: "__(Delete)",
|
||||
iconclass: "fa fa-question-circle",
|
||||
text: __("Do you really want to delete: {0}?", file.filename)
|
||||
})
|
||||
.then(d => {
|
||||
if (!d) { return; }
|
||||
return file.path.asFileHandle().remove()
|
||||
.catch(e => {
|
||||
return this.error(__("Fail to delete: {0}", file.path), e);
|
||||
});
|
||||
});
|
||||
|
||||
case `${this.name}-cut`:
|
||||
if (!file) { return; }
|
||||
this.clipboard = {
|
||||
cut: true,
|
||||
file: file.path.asFileHandle()
|
||||
};
|
||||
return this.notify(__("File {0} cut", file.filename));
|
||||
|
||||
case `${this.name}-copy`:
|
||||
if (!file && (file.type !== "dir")) { return; }
|
||||
this.clipboard = {
|
||||
cut: false,
|
||||
file: file.path.asFileHandle()
|
||||
};
|
||||
return this.notify(__("File {0} copied", file.filename));
|
||||
|
||||
case `${this.name}-paste`:
|
||||
if (!this.clipboard) { return; }
|
||||
if (this.clipboard.cut) {
|
||||
return this.clipboard.file.move(`${this.currdir.path}/${this.clipboard.file.basename}`)
|
||||
.then(r => {
|
||||
return this.clipboard = undefined;
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
|
||||
});
|
||||
} else {
|
||||
return this.clipboard.file.read("binary")
|
||||
.then(d => {
|
||||
const blob = new Blob([d], { type: this.clipboard.file.info.mime });
|
||||
const fp = `${this.currdir.path}/${this.clipboard.file.basename}`.asFileHandle();
|
||||
fp.cache = blob;
|
||||
return fp.write(this.clipboard.file.info.mime)
|
||||
.then(r => {
|
||||
return this.clipboard = undefined;
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
|
||||
});
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to read: {0}", this.clipboard.file.path), e);
|
||||
});
|
||||
}
|
||||
default:
|
||||
return this._api.handle.setting();
|
||||
}
|
||||
}
|
||||
|
||||
actionFile(e) {
|
||||
const file = this.view.get("selectedFile");
|
||||
switch (e) {
|
||||
case `${this.name}-mkdir`:
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: "__(New folder)",
|
||||
label: "__(Folder name)"
|
||||
})
|
||||
.then(d => {
|
||||
return this.currdir.mk(d)
|
||||
.catch(e => {
|
||||
return this.error(__("Fail to create: {0}", d), e);
|
||||
});
|
||||
});
|
||||
|
||||
case `${this.name}-mkf`:
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: "__(New file)",
|
||||
label: "__(File name)"
|
||||
})
|
||||
.then(d => {
|
||||
const fp = `${this.currdir.path}/${d}`.asFileHandle();
|
||||
return fp.write("text/plain")
|
||||
.catch(e => {
|
||||
return this.error(__("Fail to create: {0}", fp.path));
|
||||
});
|
||||
});
|
||||
|
||||
case `${this.name}-info`:
|
||||
if (!file) { return; }
|
||||
return this.openDialog("InfoDialog", file);
|
||||
|
||||
case `${this.name}-upload`:
|
||||
return this.currdir.upload()
|
||||
.catch(e => {
|
||||
return this.error(__("Fail to upload: {0}", e.toString()), e);
|
||||
});
|
||||
|
||||
case `${this.name}-share`:
|
||||
if (!file || (file.type !== "file")) { return; }
|
||||
return file.path.asFileHandle().publish()
|
||||
.then(r => {
|
||||
return this.notify(__("Shared url: {0}", r.result));
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to publish: {0}", file.path), e);
|
||||
});
|
||||
|
||||
case `${this.name}-download`:
|
||||
if (file.type !== "file") { return; }
|
||||
return file.path.asFileHandle().download()
|
||||
.catch(e => {
|
||||
return this.error(__("Fail to download: {0}", file.path), e);
|
||||
});
|
||||
default:
|
||||
return console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.OS.register("Files", Files);
|
@ -1,6 +1,6 @@
|
||||
coffee_files = dialog.coffee main.coffee
|
||||
module_files = dialog.js main.js
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
|
@ -1,84 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class RepositoryDialog extends this.OS.GUI.subwindows.SelectionDialog
|
||||
constructor: () ->
|
||||
super()
|
||||
|
||||
main: () ->
|
||||
super.main()
|
||||
@list = @find "list"
|
||||
$((@find "btnOk")).hide()
|
||||
@list.set "buttons", [
|
||||
{
|
||||
text: "+",
|
||||
onbtclick: () =>
|
||||
@openDialog("PromptDialog", {
|
||||
title: __("Add repository"),
|
||||
label: __("Format : [name] url")
|
||||
}).then (e) =>
|
||||
m = e.match /\[([^\]]*)\]\s*(.+)/
|
||||
if not m or m.length isnt 3
|
||||
return @error __("Wrong format: it should be [name] url")
|
||||
repo = {
|
||||
url: m[2],
|
||||
text: m[1]
|
||||
}
|
||||
@systemsetting.system.repositories.push repo
|
||||
@list.push repo
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
onbtclick: () =>
|
||||
el = @list.get "selectedItem"
|
||||
return unless el
|
||||
selidx = $(el).index()
|
||||
return unless selidx >= 0
|
||||
@systemsetting.system.repositories.splice selidx, selidx
|
||||
@list.remove el
|
||||
},
|
||||
{
|
||||
iconclass: "fa fa-pencil",
|
||||
onbtclick: () => @editRepo()
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
editRepo: () ->
|
||||
el = @list.get "selectedItem"
|
||||
return unless el
|
||||
selidx = $(el).index()
|
||||
return unless selidx >= 0
|
||||
data = el.get "data"
|
||||
sel = @systemsetting.system.repositories[selidx]
|
||||
@openDialog("PromptDialog", {
|
||||
title: __("Edit repository"),
|
||||
label: __("Format : [name] url"),
|
||||
value: "[#{data.text}] #{data.url}"
|
||||
}).then (e) =>
|
||||
m = e.match /\[([^\]]*)\]\s*(.+)/
|
||||
if not m or m.length isnt 3
|
||||
return @error __("Wrong format: it should be [name] url")
|
||||
data.text = m[1]
|
||||
data.url = m[2]
|
||||
@list.update()
|
||||
@list.unselect()
|
||||
|
||||
onexit: (e) ->
|
||||
@parent.refreshRepoList()
|
||||
super.onexit e
|
100
src/packages/MarketPlace/dialog.js
Normal file
100
src/packages/MarketPlace/dialog.js
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class RepositoryDialog extends this.OS.GUI.subwindows.SelectionDialog {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
main() {
|
||||
super.main();
|
||||
this.list = this.find("list");
|
||||
$((this.find("btnOk"))).hide();
|
||||
return this.list.set("buttons", [
|
||||
{
|
||||
text: "+",
|
||||
onbtclick: () => {
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: __("Add repository"),
|
||||
label: __("Format : [name] url")
|
||||
}).then(e => {
|
||||
const m = e.match(/\[([^\]]*)\]\s*(.+)/);
|
||||
if (!m || (m.length !== 3)) {
|
||||
return this.error(__("Wrong format: it should be [name] url"));
|
||||
}
|
||||
const repo = {
|
||||
url: m[2],
|
||||
text: m[1]
|
||||
};
|
||||
this.systemsetting.system.repositories.push(repo);
|
||||
return this.list.push(repo);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
onbtclick: () => {
|
||||
const el = this.list.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
const selidx = $(el).index();
|
||||
if (!(selidx >= 0)) { return; }
|
||||
this.systemsetting.system.repositories.splice(selidx, selidx);
|
||||
return this.list.remove(el);
|
||||
}
|
||||
},
|
||||
{
|
||||
iconclass: "fa fa-pencil",
|
||||
onbtclick: () => this.editRepo()
|
||||
}
|
||||
|
||||
]);
|
||||
}
|
||||
|
||||
editRepo() {
|
||||
const el = this.list.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
const selidx = $(el).index();
|
||||
if (!(selidx >= 0)) { return; }
|
||||
const data = el.get("data");
|
||||
const sel = this.systemsetting.system.repositories[selidx];
|
||||
return this.openDialog("PromptDialog", {
|
||||
title: __("Edit repository"),
|
||||
label: __("Format : [name] url"),
|
||||
value: `[${data.text}] ${data.url}`
|
||||
}).then(e => {
|
||||
const m = e.match(/\[([^\]]*)\]\s*(.+)/);
|
||||
if (!m || (m.length !== 3)) {
|
||||
return this.error(__("Wrong format: it should be [name] url"));
|
||||
}
|
||||
data.text = m[1];
|
||||
data.url = m[2];
|
||||
this.list.update();
|
||||
return this.list.unselect();
|
||||
});
|
||||
}
|
||||
|
||||
onexit(e) {
|
||||
this.parent.refreshRepoList();
|
||||
return super.onexit(e);
|
||||
}
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class MarketPlace extends this.OS.GUI.BaseApplication
|
||||
constructor: (args) ->
|
||||
super "MarketPlace", args
|
||||
|
||||
main: () ->
|
||||
@installdir = @systemsetting.system.pkgpaths.user
|
||||
# test repository
|
||||
@apps_meta = []
|
||||
@repo = @find "repo"
|
||||
@repo.set "onlistselect", (e) =>
|
||||
data = e.data.item.get("data")
|
||||
return unless data
|
||||
@fetchApps data
|
||||
|
||||
@refreshRepoList()
|
||||
|
||||
@applist = @find "applist"
|
||||
@applist.set "onlistselect", (e) =>
|
||||
data = e.data.item.get("data")
|
||||
@appDetail data
|
||||
|
||||
@container = @find "container"
|
||||
@appname = @find "appname"
|
||||
@appdesc = @find "app-desc"
|
||||
@appdetail = @find "app-detail"
|
||||
@btinstall = @find "bt-install"
|
||||
@btremove = @find "bt-remove"
|
||||
@btexec = @find "bt-exec"
|
||||
@searchbox = @find "searchbox"
|
||||
($ @container).css "visibility", "hidden"
|
||||
@btexec.set "onbtclick", (e) =>
|
||||
el = @applist.get "selectedItem"
|
||||
return unless el
|
||||
app = el.get("data")
|
||||
@_gui.launch app.pkgname if app.pkgname
|
||||
|
||||
@btinstall.set "onbtclick", (e) =>
|
||||
if @btinstall.get "dirty"
|
||||
return @updatePackage()
|
||||
.then () => @notify __("Package updated")
|
||||
.catch (e) => @error e.toString(), e
|
||||
@remoteInstall()
|
||||
.then (n) => @notify __("Package installed: {0}", n)
|
||||
.catch (e) => @error e.toString(), e
|
||||
|
||||
@btremove.set "onbtclick", (e) =>
|
||||
@uninstall()
|
||||
.then () => @notify __("Packaged uninstalled")
|
||||
.catch (e) => @error e.toString(), e
|
||||
|
||||
@bindKey "CTRL-R", () =>
|
||||
@menuOptionsHandle "repos"
|
||||
|
||||
$(@searchbox).keyup (e) => @search e
|
||||
|
||||
refreshRepoList: () ->
|
||||
list = (v for v in @systemsetting.system.repositories)
|
||||
list.unshift {
|
||||
text: "Installed"
|
||||
}
|
||||
@repo.set "data", list
|
||||
|
||||
search: (e) ->
|
||||
switch e.which
|
||||
when 37
|
||||
e.preventDefault()
|
||||
when 38
|
||||
@applist.selectPrev()
|
||||
e.preventDefault()
|
||||
when 39
|
||||
e.preventDefault()
|
||||
when 40
|
||||
@applist.selectNext()
|
||||
e.preventDefault()
|
||||
when 13
|
||||
e.preventDefault()
|
||||
else
|
||||
text = @searchbox.value
|
||||
@applist.set "data", (v for v in @apps_meta) if text.length is 2
|
||||
return if text.length < 3
|
||||
result = []
|
||||
term = new RegExp text, 'i'
|
||||
result.push v for v in @apps_meta when v.text.match term
|
||||
@applist.set "data", result
|
||||
|
||||
|
||||
fetchApps: (data) ->
|
||||
if not data.url
|
||||
pkgcache = @systemsetting.system.packages
|
||||
list = []
|
||||
for k, v of pkgcache
|
||||
list.push {
|
||||
pkgname: if v.pkgname then v.pkgname else v.app,
|
||||
name: v.name,
|
||||
text: v.name,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: "#{v.path}/REAME.md"
|
||||
}
|
||||
@apps_meta = list
|
||||
@applist.set "data", list
|
||||
return
|
||||
|
||||
@_api.get (data.url + "?_=" + (new Date().getTime())) , "json"
|
||||
.then ( d ) =>
|
||||
for v in d
|
||||
v.text = v.name
|
||||
v.iconclass = "fa fa-adn"
|
||||
@apps_meta = d
|
||||
@applist.set "data", d
|
||||
.catch (e) =>
|
||||
@error __("Fail to fetch packages list from: {0}", data.url), e
|
||||
|
||||
appDetail: (d) ->
|
||||
($ @container).css "visibility", "visible"
|
||||
( $ @appname ).html d.name
|
||||
(@find "vstat").set "text", ""
|
||||
if d.description
|
||||
d.description.asFileHandle().read().then (text) =>
|
||||
converter = new showdown.Converter()
|
||||
($ @appdesc).html(converter.makeHtml text)
|
||||
.catch (e) =>
|
||||
@notify __("Unable to read package description")
|
||||
($ @appdesc).empty()
|
||||
else
|
||||
($ @appdesc).empty()
|
||||
pkgcache = @systemsetting.system.packages
|
||||
@btinstall.set "text", "__(Install)"
|
||||
@btinstall.set "dirty", false
|
||||
if pkgcache[d.pkgname]
|
||||
vs = pkgcache[d.pkgname].version
|
||||
ovs = d.version
|
||||
($ @btinstall).hide()
|
||||
if vs and ovs
|
||||
vs = vs.__v()
|
||||
ovs = ovs.__v()
|
||||
if ovs.nt vs
|
||||
@btinstall.set "dirty", true
|
||||
@btinstall.set "text", "__(Update)"
|
||||
($ @btinstall).show()
|
||||
(@find "vstat").set "text",
|
||||
__("Your application version is older ({0} < {1})", vs, ovs)
|
||||
($ @btremove).show()
|
||||
($ @btexec).show()
|
||||
else
|
||||
($ @btinstall).show()
|
||||
($ @btremove).hide()
|
||||
($ @btexec).hide()
|
||||
|
||||
($ @appdetail).empty()
|
||||
for k, v of d when k isnt "name" and k isnt "description" and k isnt "domel"
|
||||
($ @appdetail).append(
|
||||
$("<li>")
|
||||
.append(($ "<span class= 'info-header'>").html k)
|
||||
.append $("<span>").html v
|
||||
)
|
||||
|
||||
menu: () ->
|
||||
return [
|
||||
{
|
||||
text: "__(Options)", child: [
|
||||
{ text: "__(Repositories)", shortcut: "C-R", id: "repos" },
|
||||
{ text: "__(Install from zip)", shortcut: "C-I", id: "install" }
|
||||
] , onchildselect: (e) =>
|
||||
@menuOptionsHandle e.data.item.get("data").id
|
||||
}
|
||||
]
|
||||
|
||||
menuOptionsHandle: (id) ->
|
||||
switch id
|
||||
when "repos"
|
||||
@openDialog new RepositoryDialog(), {
|
||||
title: __("Repositories"),
|
||||
data: @systemsetting.system.repositories
|
||||
}
|
||||
when "install"
|
||||
@localInstall().then (n) =>
|
||||
@notify __("Package installed: {0}", n)
|
||||
.catch (e) => @error __("Unable to install package"), e
|
||||
else
|
||||
|
||||
remoteInstall: () ->
|
||||
el = @applist.get "selectedItem"
|
||||
return unless el
|
||||
app = el.get "data"
|
||||
return unless app
|
||||
# get blob file
|
||||
new Promise (resolve, reject) =>
|
||||
@_api.blob app.download + "?_=" + (new Date().getTime())
|
||||
.then (data) =>
|
||||
@install data, app
|
||||
.then (n) -> resolve(n)
|
||||
.catch (e) -> reject(__e e)
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
localInstall: () ->
|
||||
new Promise (resolve, reject) =>
|
||||
@openDialog("FileDialog", {
|
||||
title: "__(Select package archive)",
|
||||
mimes: [".*/zip"]
|
||||
}).then (d) =>
|
||||
d.file.path.asFileHandle().read("binary").then (data) =>
|
||||
@install data
|
||||
.then (n) =>
|
||||
@repo.unselect()
|
||||
@repo.set "selected", 0
|
||||
apps = (v.pkgname for v in @applist.get("data"))
|
||||
idx = apps.indexOf n
|
||||
if idx >= 0
|
||||
@applist.set "selected", idx
|
||||
resolve(n)
|
||||
.catch (e) -> reject(__e e)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
install: (data, meta) ->
|
||||
new Promise (resolve, reject) =>
|
||||
JSZip.loadAsync(data).then (zip) =>
|
||||
zip.file("package.json").async("string").then (d) =>
|
||||
v = JSON.parse d
|
||||
pth = "#{@installdir}/#{v.app}"
|
||||
dir = [pth]
|
||||
files = []
|
||||
for name, file of zip.files
|
||||
if file.dir
|
||||
dir.push(pth + "/" + name)
|
||||
else
|
||||
files.push name
|
||||
# create all directory
|
||||
@mkdirs(dir).then () =>
|
||||
@installFile(v.app, zip, files).then () =>
|
||||
app_meta = {
|
||||
pkgname: v.app,
|
||||
name: v.name,
|
||||
text: v.name,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: if meta then meta.description else undefined,
|
||||
download: if meta then meta.download else undefined
|
||||
}
|
||||
v.text = v.name
|
||||
v.filename = v.app
|
||||
v.type = "app"
|
||||
v.mime = "antos/app"
|
||||
v.iconclass = "fa fa-adn" unless v.iconclass or v.icon
|
||||
v.path = pth
|
||||
@systemsetting.system.packages[v.app] = v
|
||||
@appDetail app_meta
|
||||
resolve(v.name)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
.catch (err) -> reject __e err
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
uninstall: () ->
|
||||
new Promise (resolve, reject) =>
|
||||
el = @applist.get "selectedItem"
|
||||
return unless el
|
||||
sel = el.get "data"
|
||||
return unless sel
|
||||
name = sel.pkgname
|
||||
app = @systemsetting.system.packages[sel.pkgname]
|
||||
return unless app
|
||||
@openDialog("YesNoDialog", {
|
||||
title: __("Uninstall") ,
|
||||
text: __("Uninstall: {0}?", app.name)
|
||||
}).then (d) =>
|
||||
return unless d
|
||||
app.path.asFileHandle().remove().then (r) =>
|
||||
if r.error
|
||||
return reject @_api.throwe __("Cannot uninstall package: {0}", r.error)
|
||||
@notify __("Package uninstalled")
|
||||
# stop all the services if any
|
||||
if app.services
|
||||
for srv in app.services
|
||||
@_gui.unloadApp srv
|
||||
|
||||
delete @systemsetting.system.packages[name]
|
||||
@_gui.unloadApp name
|
||||
if sel.download
|
||||
@appDetail sel
|
||||
else
|
||||
@applist.remove el
|
||||
($ @container).css "visibility", "hidden"
|
||||
resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
updatePackage: () ->
|
||||
new Promise (resolve, reject) =>
|
||||
@uninstall().then () =>
|
||||
@remoteInstall()
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
mkdirs: (list) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if list.length is 0
|
||||
dir = (list.splice 0, 1)[0].asFileHandle()
|
||||
path = dir.parent()
|
||||
dname = dir.basename
|
||||
path.asFileHandle().mk dname
|
||||
.then (r) =>
|
||||
return reject(@_api.throwe __("Cannot create {0}", "#{path}/#{dir}")) if r.error
|
||||
@mkdirs list
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
installFile: (n, zip, files) ->
|
||||
new Promise (resolve, reject) =>
|
||||
return resolve() if files.length is 0
|
||||
file = (files.splice 0, 1)[0]
|
||||
path = "#{@installdir}/#{n}/#{file}"
|
||||
zip.file(file).async("uint8array").then (d) =>
|
||||
fp = path.asFileHandle()
|
||||
fp.cache = new Blob [d], { type: "octet/stream" }
|
||||
fp.write "text/plain"
|
||||
.then (r) =>
|
||||
return reject @_api.throwe(__("Cannot install {0}", path)) if r.error
|
||||
@installFile n, zip, files
|
||||
.then () -> resolve()
|
||||
.catch (e) -> reject( __e e)
|
||||
.catch (e) -> reject __e e
|
||||
.catch (e) -> reject __e e
|
||||
|
||||
MarketPlace.dependencies = [
|
||||
"os://scripts/jszip.min.js",
|
||||
"os://scripts/showdown.min.js"
|
||||
]
|
||||
MarketPlace.singleton = true
|
||||
this.OS.register "MarketPlace", MarketPlace
|
433
src/packages/MarketPlace/main.js
Normal file
433
src/packages/MarketPlace/main.js
Normal file
@ -0,0 +1,433 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class MarketPlace extends this.OS.GUI.BaseApplication {
|
||||
constructor(args) {
|
||||
super("MarketPlace", args);
|
||||
}
|
||||
|
||||
main() {
|
||||
this.installdir = this.systemsetting.system.pkgpaths.user;
|
||||
// test repository
|
||||
this.apps_meta = [];
|
||||
this.repo = this.find("repo");
|
||||
this.repo.set("onlistselect", e => {
|
||||
const data = e.data.item.get("data");
|
||||
if (!data) { return; }
|
||||
return this.fetchApps(data);
|
||||
});
|
||||
|
||||
this.refreshRepoList();
|
||||
|
||||
this.applist = this.find("applist");
|
||||
this.applist.set("onlistselect", e => {
|
||||
const data = e.data.item.get("data");
|
||||
return this.appDetail(data);
|
||||
});
|
||||
|
||||
this.container = this.find("container");
|
||||
this.appname = this.find("appname");
|
||||
this.appdesc = this.find("app-desc");
|
||||
this.appdetail = this.find("app-detail");
|
||||
this.btinstall = this.find("bt-install");
|
||||
this.btremove = this.find("bt-remove");
|
||||
this.btexec = this.find("bt-exec");
|
||||
this.searchbox = this.find("searchbox");
|
||||
($(this.container)).css("visibility", "hidden");
|
||||
this.btexec.set("onbtclick", e => {
|
||||
const el = this.applist.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
const app = el.get("data");
|
||||
if (app.pkgname) { return this._gui.launch(app.pkgname); }
|
||||
});
|
||||
|
||||
this.btinstall.set("onbtclick", e => {
|
||||
if (this.btinstall.get("dirty")) {
|
||||
return this.updatePackage()
|
||||
.then(() => this.notify(__("Package updated")))
|
||||
.catch(e => this.error(e.toString(), e));
|
||||
}
|
||||
return this.remoteInstall()
|
||||
.then(n => this.notify(__("Package installed: {0}", n)))
|
||||
.catch(e => this.error(e.toString(), e));
|
||||
});
|
||||
|
||||
this.btremove.set("onbtclick", e => {
|
||||
return this.uninstall()
|
||||
.then(() => this.notify(__("Packaged uninstalled")))
|
||||
.catch(e => this.error(e.toString(), e));
|
||||
});
|
||||
|
||||
this.bindKey("CTRL-R", () => {
|
||||
return this.menuOptionsHandle("repos");
|
||||
});
|
||||
|
||||
return $(this.searchbox).keyup(e => this.search(e));
|
||||
}
|
||||
|
||||
refreshRepoList() {
|
||||
const list = (Array.from(this.systemsetting.system.repositories));
|
||||
list.unshift({
|
||||
text: "Installed"
|
||||
});
|
||||
return this.repo.set("data", list);
|
||||
}
|
||||
|
||||
search(e) {
|
||||
let v;
|
||||
switch (e.which) {
|
||||
case 37:
|
||||
return e.preventDefault();
|
||||
case 38:
|
||||
this.applist.selectPrev();
|
||||
return e.preventDefault();
|
||||
case 39:
|
||||
return e.preventDefault();
|
||||
case 40:
|
||||
this.applist.selectNext();
|
||||
return e.preventDefault();
|
||||
case 13:
|
||||
return e.preventDefault();
|
||||
default:
|
||||
var text = this.searchbox.value;
|
||||
if (text.length === 2) { this.applist.set("data", ((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(this.apps_meta)) { result1.push(v);
|
||||
}
|
||||
return result1;
|
||||
})())); }
|
||||
if (text.length < 3) { return; }
|
||||
var result = [];
|
||||
var term = new RegExp(text, 'i');
|
||||
for (v of Array.from(this.apps_meta)) { if (v.text.match(term)) { result.push(v); } }
|
||||
return this.applist.set("data", result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fetchApps(data) {
|
||||
let v;
|
||||
if (!data.url) {
|
||||
const pkgcache = this.systemsetting.system.packages;
|
||||
const list = [];
|
||||
for (let k in pkgcache) {
|
||||
v = pkgcache[k];
|
||||
list.push({
|
||||
pkgname: v.pkgname ? v.pkgname : v.app,
|
||||
name: v.name,
|
||||
text: v.name,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: `${v.path}/REAME.md`
|
||||
});
|
||||
}
|
||||
this.apps_meta = list;
|
||||
this.applist.set("data", list);
|
||||
return;
|
||||
}
|
||||
|
||||
return this._api.get((data.url + "?_=" + (new Date().getTime())) , "json")
|
||||
.then(d => {
|
||||
for (v of Array.from(d)) {
|
||||
v.text = v.name;
|
||||
v.iconclass = "fa fa-adn";
|
||||
}
|
||||
this.apps_meta = d;
|
||||
return this.applist.set("data", d);
|
||||
}).catch(e => {
|
||||
return this.error(__("Fail to fetch packages list from: {0}", data.url), e);
|
||||
});
|
||||
}
|
||||
|
||||
appDetail(d) {
|
||||
($(this.container)).css("visibility", "visible");
|
||||
( $(this.appname) ).html(d.name);
|
||||
(this.find("vstat")).set("text", "");
|
||||
if (d.description) {
|
||||
d.description.asFileHandle().read().then(text => {
|
||||
const converter = new showdown.Converter();
|
||||
return ($(this.appdesc)).html(converter.makeHtml(text));
|
||||
}).catch(e => {
|
||||
this.notify(__("Unable to read package description"));
|
||||
return ($(this.appdesc)).empty();
|
||||
});
|
||||
} else {
|
||||
($(this.appdesc)).empty();
|
||||
}
|
||||
const pkgcache = this.systemsetting.system.packages;
|
||||
this.btinstall.set("text", "__(Install)");
|
||||
this.btinstall.set("dirty", false);
|
||||
if (pkgcache[d.pkgname]) {
|
||||
let vs = pkgcache[d.pkgname].version;
|
||||
let ovs = d.version;
|
||||
($(this.btinstall)).hide();
|
||||
if (vs && ovs) {
|
||||
vs = vs.__v();
|
||||
ovs = ovs.__v();
|
||||
if (ovs.nt(vs)) {
|
||||
this.btinstall.set("dirty", true);
|
||||
this.btinstall.set("text", "__(Update)");
|
||||
($(this.btinstall)).show();
|
||||
(this.find("vstat")).set("text",
|
||||
__("Your application version is older ({0} < {1})", vs, ovs));
|
||||
}
|
||||
}
|
||||
($(this.btremove)).show();
|
||||
($(this.btexec)).show();
|
||||
} else {
|
||||
($(this.btinstall)).show();
|
||||
($(this.btremove)).hide();
|
||||
($(this.btexec)).hide();
|
||||
}
|
||||
|
||||
($(this.appdetail)).empty();
|
||||
return (() => {
|
||||
const result = [];
|
||||
for (let k in d) {
|
||||
const v = d[k];
|
||||
if ((k !== "name") && (k !== "description") && (k !== "domel")) {
|
||||
result.push(($(this.appdetail)).append(
|
||||
$("<li>")
|
||||
.append(($("<span class= 'info-header'>")).html(k))
|
||||
.append($("<span>").html(v))
|
||||
));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
|
||||
menu() {
|
||||
return [
|
||||
{
|
||||
text: "__(Options)", child: [
|
||||
{ text: "__(Repositories)", shortcut: "C-R", id: "repos" },
|
||||
{ text: "__(Install from zip)", shortcut: "C-I", id: "install" }
|
||||
] , onchildselect: e => {
|
||||
return this.menuOptionsHandle(e.data.item.get("data").id);
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
menuOptionsHandle(id) {
|
||||
switch (id) {
|
||||
case "repos":
|
||||
return this.openDialog(new RepositoryDialog(), {
|
||||
title: __("Repositories"),
|
||||
data: this.systemsetting.system.repositories
|
||||
});
|
||||
case "install":
|
||||
return this.localInstall().then(n => {
|
||||
return this.notify(__("Package installed: {0}", n));
|
||||
}).catch(e => this.error(__("Unable to install package"), e));
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
remoteInstall() {
|
||||
const el = this.applist.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
const app = el.get("data");
|
||||
if (!app) { return; }
|
||||
// get blob file
|
||||
return new Promise((resolve, reject) => {
|
||||
return this._api.blob(app.download + "?_=" + (new Date().getTime()))
|
||||
.then(data => {
|
||||
return this.install(data, app)
|
||||
.then(n => resolve(n))
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
localInstall() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.openDialog("FileDialog", {
|
||||
title: "__(Select package archive)",
|
||||
mimes: [".*/zip"]
|
||||
}).then(d => {
|
||||
return d.file.path.asFileHandle().read("binary").then(data => {
|
||||
return this.install(data)
|
||||
.then(n => {
|
||||
this.repo.unselect();
|
||||
this.repo.set("selected", 0);
|
||||
const apps = (Array.from(this.applist.get("data")).map((v) => v.pkgname));
|
||||
const idx = apps.indexOf(n);
|
||||
if (idx >= 0) {
|
||||
this.applist.set("selected", idx);
|
||||
}
|
||||
return resolve(n);
|
||||
}).catch(e => reject(__e(e)))
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
install(data, meta) {
|
||||
return new Promise((resolve, reject) => {
|
||||
return JSZip.loadAsync(data).then(zip => {
|
||||
return zip.file("package.json").async("string").then(d => {
|
||||
let name;
|
||||
const v = JSON.parse(d);
|
||||
const pth = `${this.installdir}/${v.app}`;
|
||||
const dir = [pth];
|
||||
const files = [];
|
||||
for (name in zip.files) {
|
||||
const file = zip.files[name];
|
||||
if (file.dir) {
|
||||
dir.push(pth + "/" + name);
|
||||
} else {
|
||||
files.push(name);
|
||||
}
|
||||
}
|
||||
// create all directory
|
||||
return this.mkdirs(dir).then(() => {
|
||||
return this.installFile(v.app, zip, files).then(() => {
|
||||
const app_meta = {
|
||||
pkgname: v.app,
|
||||
name: v.name,
|
||||
text: v.name,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: meta ? meta.description : undefined,
|
||||
download: meta ? meta.download : undefined
|
||||
};
|
||||
v.text = v.name;
|
||||
v.filename = v.app;
|
||||
v.type = "app";
|
||||
v.mime = "antos/app";
|
||||
if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; }
|
||||
v.path = pth;
|
||||
this.systemsetting.system.packages[v.app] = v;
|
||||
this.appDetail(app_meta);
|
||||
return resolve(v.name);
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(err => reject(__e(err)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const el = this.applist.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
const sel = el.get("data");
|
||||
if (!sel) { return; }
|
||||
const name = sel.pkgname;
|
||||
const app = this.systemsetting.system.packages[sel.pkgname];
|
||||
if (!app) { return; }
|
||||
return this.openDialog("YesNoDialog", {
|
||||
title: __("Uninstall") ,
|
||||
text: __("Uninstall: {0}?", app.name)
|
||||
}).then(d => {
|
||||
if (!d) { return; }
|
||||
return app.path.asFileHandle().remove().then(r => {
|
||||
if (r.error) {
|
||||
return reject(this._api.throwe(__("Cannot uninstall package: {0}", r.error)));
|
||||
}
|
||||
this.notify(__("Package uninstalled"));
|
||||
// stop all the services if any
|
||||
if (app.services) {
|
||||
for (let srv of Array.from(app.services)) {
|
||||
this._gui.unloadApp(srv);
|
||||
}
|
||||
}
|
||||
|
||||
delete this.systemsetting.system.packages[name];
|
||||
this._gui.unloadApp(name);
|
||||
if (sel.download) {
|
||||
this.appDetail(sel);
|
||||
} else {
|
||||
this.applist.remove(el);
|
||||
($(this.container)).css("visibility", "hidden");
|
||||
}
|
||||
return resolve();
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
updatePackage() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.uninstall().then(() => {
|
||||
return this.remoteInstall()
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
mkdirs(list) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (list.length === 0) { return resolve(); }
|
||||
const dir = (list.splice(0, 1))[0].asFileHandle();
|
||||
const path = dir.parent();
|
||||
const dname = dir.basename;
|
||||
return path.asFileHandle().mk(dname)
|
||||
.then(r => {
|
||||
if (r.error) { return reject(this._api.throwe(__("Cannot create {0}", `${path}/${dir}`))); }
|
||||
return this.mkdirs(list)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
|
||||
installFile(n, zip, files) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (files.length === 0) { return resolve(); }
|
||||
const file = (files.splice(0, 1))[0];
|
||||
const path = `${this.installdir}/${n}/${file}`;
|
||||
return zip.file(file).async("uint8array").then(d => {
|
||||
const fp = path.asFileHandle();
|
||||
fp.cache = new Blob([d], { type: "octet/stream" });
|
||||
return fp.write("text/plain")
|
||||
.then(r => {
|
||||
if (r.error) { return reject(this._api.throwe(__("Cannot install {0}", path))); }
|
||||
return this.installFile(n, zip, files)
|
||||
.then(() => resolve())
|
||||
.catch(e => reject( __e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
}).catch(e => reject(__e(e)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MarketPlace.dependencies = [
|
||||
"os://scripts/jszip.min.js",
|
||||
"os://scripts/showdown.min.js"
|
||||
];
|
||||
MarketPlace.singleton = true;
|
||||
this.OS.register("MarketPlace", MarketPlace);
|
@ -1,104 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class AppearanceHandle extends SettingHandle
|
||||
constructor:(scheme, parent) ->
|
||||
super(scheme, parent)
|
||||
@wplist = @find "wplist"
|
||||
@wpreview = @find "wp-preview"
|
||||
@wpsize = @find "wpsize"
|
||||
@wprepeat = @find "wprepeat"
|
||||
@themelist = @find "theme-list"
|
||||
@syswp = undefined
|
||||
@wplist.set "onlistselect", (e) =>
|
||||
data = e.data.item.get("data")
|
||||
$(@wpreview)
|
||||
.css("background-image", "url(#{data.path.asFileHandle().getlink()})" )
|
||||
.css("background-size", "cover")
|
||||
@parent.systemsetting.appearance.wp.url = data.path
|
||||
@parent._gui.wallpaper()
|
||||
|
||||
@wplist.set "buttons", [
|
||||
{
|
||||
text: "+", onbtclick: (e) =>
|
||||
@parent.openDialog("FileDialog", {
|
||||
title: __("Select image file"),
|
||||
mimes: ["image/.*"]
|
||||
}).then (d) =>
|
||||
@parent.systemsetting.appearance.wps.push d.file.path
|
||||
@wplist.set "data", @getwplist()
|
||||
}
|
||||
]
|
||||
|
||||
@wpsize.set "onlistselect", (e) =>
|
||||
@parent.systemsetting.appearance.wp.size = e.data.item.get("data").text
|
||||
@parent._gui.wallpaper()
|
||||
|
||||
sizes = [
|
||||
{ text: "cover", selected: @parent.systemsetting.appearance.wp.size is "cover" },
|
||||
{ text: "auto", selected: @parent.systemsetting.appearance.wp.size is "auto" },
|
||||
{ text: "contain", selected: @parent.systemsetting.appearance.wp.size is "contain" }
|
||||
]
|
||||
@wpsize.set "data", sizes
|
||||
|
||||
repeats = [
|
||||
{ text: "repeat", selected: @parent.systemsetting.appearance.wp.repeat is "repeat" },
|
||||
{ text: "repeat-x", selected: @parent.systemsetting.appearance.wp.repeat is "repeat-x" },
|
||||
{ text: "repeat-y", selected: @parent.systemsetting.appearance.wp.repeat is "repeat-y" },
|
||||
{ text: "no-repeat", selected: @parent.systemsetting.appearance.wp.repeat is "no-repeat" }
|
||||
]
|
||||
@wprepeat.set "onlistselect", (e) =>
|
||||
@parent.systemsetting.appearance.wp.repeat = e.data.item.get("data").text
|
||||
@parent._gui.wallpaper()
|
||||
@wprepeat.set "data", repeats
|
||||
currtheme = @parent.systemsetting.appearance.theme
|
||||
v.selected = v.name is currtheme for v in @parent.systemsetting.appearance.themes
|
||||
@themelist.set "data" , @parent.systemsetting.appearance.themes
|
||||
@themelist.set "onlistselect", (e) =>
|
||||
data = e.data.item.get("data") if e and e.data
|
||||
return unless data
|
||||
return if data.name is @parent.systemsetting.appearance.theme
|
||||
@parent.systemsetting.appearance.theme = data.name
|
||||
@parent._gui.loadTheme data.name, true
|
||||
if not @syswp
|
||||
path = "os://resources/themes/system/wp"
|
||||
path.asFileHandle().read()
|
||||
.then (d) =>
|
||||
return @parent.error __("Cannot read wallpaper list from {0}", path) if d.error
|
||||
for v in d.result
|
||||
v.text = v.filename
|
||||
v.iconclass = "fa fa-file-image-o"
|
||||
@syswp = d.result
|
||||
@wplist.set "data", @getwplist()
|
||||
.catch (e) => @parent.error __("Unable to read: {0}", path), e
|
||||
else
|
||||
|
||||
@wplist.set "data", @getwplist()
|
||||
|
||||
getwplist: () ->
|
||||
list = []
|
||||
for v in @parent.systemsetting.appearance.wps
|
||||
file = v.asFileHandle()
|
||||
list.push
|
||||
text: file.basename,
|
||||
path: file.path
|
||||
selected: file.path is @parent.systemsetting.appearance.wp.url,
|
||||
iconclass: "fa fa-file-image-o"
|
||||
list = list.concat @syswp
|
||||
v.selected = v.path is @parent.systemsetting.appearance.wp.url for v in list
|
||||
return list
|
126
src/packages/Setting/AppearanceHandle.js
Normal file
126
src/packages/Setting/AppearanceHandle.js
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class AppearanceHandle extends SettingHandle {
|
||||
constructor(scheme, parent) {
|
||||
let v;
|
||||
super(scheme, parent);
|
||||
this.wplist = this.find("wplist");
|
||||
this.wpreview = this.find("wp-preview");
|
||||
this.wpsize = this.find("wpsize");
|
||||
this.wprepeat = this.find("wprepeat");
|
||||
this.themelist = this.find("theme-list");
|
||||
this.syswp = undefined;
|
||||
this.wplist.set("onlistselect", e => {
|
||||
const data = e.data.item.get("data");
|
||||
$(this.wpreview)
|
||||
.css("background-image", `url(${data.path.asFileHandle().getlink()})` )
|
||||
.css("background-size", "cover");
|
||||
this.parent.systemsetting.appearance.wp.url = data.path;
|
||||
return this.parent._gui.wallpaper();
|
||||
});
|
||||
|
||||
this.wplist.set("buttons", [
|
||||
{
|
||||
text: "+", onbtclick: e => {
|
||||
return this.parent.openDialog("FileDialog", {
|
||||
title: __("Select image file"),
|
||||
mimes: ["image/.*"]
|
||||
}).then(d => {
|
||||
this.parent.systemsetting.appearance.wps.push(d.file.path);
|
||||
return this.wplist.set("data", this.getwplist());
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
this.wpsize.set("onlistselect", e => {
|
||||
this.parent.systemsetting.appearance.wp.size = e.data.item.get("data").text;
|
||||
return this.parent._gui.wallpaper();
|
||||
});
|
||||
|
||||
const sizes = [
|
||||
{ text: "cover", selected: this.parent.systemsetting.appearance.wp.size === "cover" },
|
||||
{ text: "auto", selected: this.parent.systemsetting.appearance.wp.size === "auto" },
|
||||
{ text: "contain", selected: this.parent.systemsetting.appearance.wp.size === "contain" }
|
||||
];
|
||||
this.wpsize.set("data", sizes);
|
||||
|
||||
const repeats = [
|
||||
{ text: "repeat", selected: this.parent.systemsetting.appearance.wp.repeat === "repeat" },
|
||||
{ text: "repeat-x", selected: this.parent.systemsetting.appearance.wp.repeat === "repeat-x" },
|
||||
{ text: "repeat-y", selected: this.parent.systemsetting.appearance.wp.repeat === "repeat-y" },
|
||||
{ text: "no-repeat", selected: this.parent.systemsetting.appearance.wp.repeat === "no-repeat" }
|
||||
];
|
||||
this.wprepeat.set("onlistselect", e => {
|
||||
this.parent.systemsetting.appearance.wp.repeat = e.data.item.get("data").text;
|
||||
return this.parent._gui.wallpaper();
|
||||
});
|
||||
this.wprepeat.set("data", repeats);
|
||||
const currtheme = this.parent.systemsetting.appearance.theme;
|
||||
for (v of Array.from(this.parent.systemsetting.appearance.themes)) { v.selected = v.name === currtheme; }
|
||||
this.themelist.set("data" , this.parent.systemsetting.appearance.themes);
|
||||
this.themelist.set("onlistselect", e => {
|
||||
let data;
|
||||
if (e && e.data) { data = e.data.item.get("data"); }
|
||||
if (!data) { return; }
|
||||
if (data.name === this.parent.systemsetting.appearance.theme) { return; }
|
||||
this.parent.systemsetting.appearance.theme = data.name;
|
||||
return this.parent._gui.loadTheme(data.name, true);
|
||||
});
|
||||
if (!this.syswp) {
|
||||
const path = "os://resources/themes/system/wp";
|
||||
path.asFileHandle().read()
|
||||
.then(d => {
|
||||
if (d.error) { return this.parent.error(__("Cannot read wallpaper list from {0}", path)); }
|
||||
for (v of Array.from(d.result)) {
|
||||
v.text = v.filename;
|
||||
v.iconclass = "fa fa-file-image-o";
|
||||
}
|
||||
this.syswp = d.result;
|
||||
return this.wplist.set("data", this.getwplist());
|
||||
}).catch(e => this.parent.error(__("Unable to read: {0}", path), e));
|
||||
} else {
|
||||
|
||||
this.wplist.set("data", this.getwplist());
|
||||
}
|
||||
}
|
||||
|
||||
getwplist() {
|
||||
let v;
|
||||
let list = [];
|
||||
for (v of Array.from(this.parent.systemsetting.appearance.wps)) {
|
||||
const file = v.asFileHandle();
|
||||
list.push({
|
||||
text: file.basename,
|
||||
path: file.path,
|
||||
selected: file.path === this.parent.systemsetting.appearance.wp.url,
|
||||
iconclass: "fa fa-file-image-o"
|
||||
});
|
||||
}
|
||||
list = list.concat(this.syswp);
|
||||
for (v of Array.from(list)) { v.selected = v.path === this.parent.systemsetting.appearance.wp.url; }
|
||||
return list;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class LocaleHandle extends SettingHandle
|
||||
constructor: (scheme, parent) ->
|
||||
super(scheme, parent)
|
||||
@lglist = @find "lglist"
|
||||
@localelist = undefined
|
||||
@lglist.set "onlistselect", (e) =>
|
||||
@parent._api.setLocale e.data.item.get("data").text
|
||||
if not @localelist
|
||||
path = "os://resources/languages"
|
||||
path.asFileHandle().read()
|
||||
.then (d) =>
|
||||
return @parent.error __("Cannot fetch system locales: {0}", d.error) if d.derror
|
||||
for v in d.result
|
||||
v.text = v.filename.replace /\.json$/g, ""
|
||||
v.selected = v.text is @parent.systemsetting.system.locale
|
||||
@localelist = d.result
|
||||
@lglist.set "data", @localelist
|
||||
.catch (e) => @parent.error __("Unable to read: {0}", path), e
|
||||
else
|
||||
@lglist.set "data", @localelist
|
49
src/packages/Setting/LocaleHandle.js
Normal file
49
src/packages/Setting/LocaleHandle.js
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class LocaleHandle extends SettingHandle {
|
||||
constructor(scheme, parent) {
|
||||
super(scheme, parent);
|
||||
this.lglist = this.find("lglist");
|
||||
this.localelist = undefined;
|
||||
this.lglist.set("onlistselect", e => {
|
||||
return this.parent._api.setLocale(e.data.item.get("data").text);
|
||||
});
|
||||
if (!this.localelist) {
|
||||
const path = "os://resources/languages";
|
||||
path.asFileHandle().read()
|
||||
.then(d => {
|
||||
if (d.derror) { return this.parent.error(__("Cannot fetch system locales: {0}", d.error)); }
|
||||
for (let v of Array.from(d.result)) {
|
||||
v.text = v.filename.replace(/\.json$/g, "");
|
||||
v.selected = v.text === this.parent.systemsetting.system.locale;
|
||||
}
|
||||
this.localelist = d.result;
|
||||
return this.lglist.set("data", this.localelist);
|
||||
}).catch(e => this.parent.error(__("Unable to read: {0}", path), e));
|
||||
} else {
|
||||
this.lglist.set("data", this.localelist);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
coffee_files = main.coffee AppearanceHandle.coffee VFSHandle.coffee LocaleHandle.coffee StartupHandle.coffee
|
||||
module_files = main.js AppearanceHandle.js VFSHandle.js LocaleHandle.js StartupHandle.js
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
|
@ -1,73 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class StartupHandle extends SettingHandle
|
||||
constructor: (scheme, parent) ->
|
||||
super(scheme, parent)
|
||||
@srvlist = @find "srvlist"
|
||||
@applist = @find "applist"
|
||||
@srvlist.set "buttons", [
|
||||
{
|
||||
text: "+", onbtclick: (e) =>
|
||||
services = []
|
||||
for k, v of @parent.systemsetting.system.packages
|
||||
if v.services
|
||||
srvs = ({ text: "#{k}/#{x}", iconclass: "fa fa-tasks" } for x in v.services)
|
||||
services = services.concat srvs
|
||||
@parent.openDialog("SelectionDialog", {
|
||||
title: "__(Add service)",
|
||||
data: services
|
||||
}).then (d) =>
|
||||
@parent.systemsetting.system.startup.services.push d.text
|
||||
@refresh()
|
||||
},
|
||||
{
|
||||
text: "-", onbtclick: (e) =>
|
||||
item = @srvlist.get "selectedItem"
|
||||
return unless item
|
||||
selidx = $(item).index()
|
||||
@parent.systemsetting.system.startup.services.splice selidx, 1
|
||||
@refresh()
|
||||
}
|
||||
]
|
||||
|
||||
@applist.set "buttons", [
|
||||
{
|
||||
text: "+", onbtclick: (e) =>
|
||||
apps = ( { text: k, iconclass: v.iconclass } for k, v of @parent.systemsetting.system.packages )
|
||||
@parent.openDialog("SelectionDialog", {
|
||||
title: "__(Add application)",
|
||||
data: apps
|
||||
}).then (d) =>
|
||||
@parent.systemsetting.system.startup.apps.push d.text
|
||||
@refresh()
|
||||
},
|
||||
{
|
||||
text: "-", onbtclick: (e) =>
|
||||
item = @applist.get "selectedItem"
|
||||
return unless item
|
||||
selidx = $(item).index()
|
||||
@parent.systemsetting.system.startup.apps.splice selidx, 1
|
||||
@refresh()
|
||||
}
|
||||
]
|
||||
@refresh()
|
||||
|
||||
refresh: () ->
|
||||
@srvlist.set "data", ( { text:v } for v in @parent.systemsetting.system.startup.services )
|
||||
@applist.set "data", ( { text:v } for v in @parent.systemsetting.system.startup.apps )
|
110
src/packages/Setting/StartupHandle.js
Normal file
110
src/packages/Setting/StartupHandle.js
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class StartupHandle extends SettingHandle {
|
||||
constructor(scheme, parent) {
|
||||
super(scheme, parent);
|
||||
this.srvlist = this.find("srvlist");
|
||||
this.applist = this.find("applist");
|
||||
this.srvlist.set("buttons", [
|
||||
{
|
||||
text: "+", onbtclick: e => {
|
||||
let services = [];
|
||||
for (var k in this.parent.systemsetting.system.packages) {
|
||||
const v = this.parent.systemsetting.system.packages[k];
|
||||
if (v.services) {
|
||||
const srvs = (Array.from(v.services).map((x) => ({ text: `${k}/${x}`, iconclass: "fa fa-tasks" })));
|
||||
services = services.concat(srvs);
|
||||
}
|
||||
}
|
||||
return this.parent.openDialog("SelectionDialog", {
|
||||
title: "__(Add service)",
|
||||
data: services
|
||||
}).then(d => {
|
||||
this.parent.systemsetting.system.startup.services.push(d.text);
|
||||
return this.refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "-", onbtclick: e => {
|
||||
const item = this.srvlist.get("selectedItem");
|
||||
if (!item) { return; }
|
||||
const selidx = $(item).index();
|
||||
this.parent.systemsetting.system.startup.services.splice(selidx, 1);
|
||||
return this.refresh();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
this.applist.set("buttons", [
|
||||
{
|
||||
text: "+", onbtclick: e => {
|
||||
const apps = ((() => {
|
||||
const result = [];
|
||||
for (let k in this.parent.systemsetting.system.packages) {
|
||||
const v = this.parent.systemsetting.system.packages[k];
|
||||
result.push({ text: k, iconclass: v.iconclass });
|
||||
}
|
||||
return result;
|
||||
})());
|
||||
return this.parent.openDialog("SelectionDialog", {
|
||||
title: "__(Add application)",
|
||||
data: apps
|
||||
}).then(d => {
|
||||
this.parent.systemsetting.system.startup.apps.push(d.text);
|
||||
return this.refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "-", onbtclick: e => {
|
||||
const item = this.applist.get("selectedItem");
|
||||
if (!item) { return; }
|
||||
const selidx = $(item).index();
|
||||
this.parent.systemsetting.system.startup.apps.splice(selidx, 1);
|
||||
return this.refresh();
|
||||
}
|
||||
}
|
||||
]);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
let v;
|
||||
this.srvlist.set("data", ((() => {
|
||||
const result = [];
|
||||
for (v of Array.from(this.parent.systemsetting.system.startup.services)) { result.push({ text:v });
|
||||
}
|
||||
return result;
|
||||
})()));
|
||||
return this.applist.set("data", ((() => {
|
||||
const result1 = [];
|
||||
for (v of Array.from(this.parent.systemsetting.system.startup.apps)) { result1.push({ text:v });
|
||||
}
|
||||
return result1;
|
||||
})()));
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class VFSSettingDialog extends this.OS.GUI.BasicDialog
|
||||
constructor: () ->
|
||||
super "VFSSettingDialog", VFSSettingDialog.scheme
|
||||
|
||||
main: () ->
|
||||
super.main()
|
||||
$(@find("txtPath")).click (e) =>
|
||||
@openDialog("FileDialog", {
|
||||
title: "__(Select a directory)",
|
||||
mimes: ["dir"],
|
||||
hidden: true
|
||||
})
|
||||
.then (d) =>
|
||||
(@find "txtPath").value = d.file.path
|
||||
|
||||
@find("btnOk").set "onbtclick", (e) =>
|
||||
data = {
|
||||
path: (@find "txtPath").value,
|
||||
name: (@find "txtName").value
|
||||
}
|
||||
return @error __("Please enter mount point name") unless data.name and data.name isnt ""
|
||||
return @error __("Please select a directory") unless data.path and data.path isnt ""
|
||||
@handle(data) if @handle
|
||||
@quit()
|
||||
|
||||
(@find "btnCancel").set "onbtclick", (e) =>
|
||||
@quit()
|
||||
|
||||
return unless @data
|
||||
(@find "txtName").value = @data.text if @data.text
|
||||
(@find "txtPath").value = @data.path if @data.path
|
||||
|
||||
VFSSettingDialog.scheme = """
|
||||
<afx-app-window width='250' height='180' apptitle = "__(Mount Points)">
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" />
|
||||
<afx-vbox>
|
||||
<div data-height="10" />
|
||||
<afx-label data-height="30" text = "__(Name)" />
|
||||
<input type = "text" data-id= "txtName" />
|
||||
<div data-height="3" />
|
||||
<afx-label data-height="30" text = "__(Path)" />
|
||||
<input type = "text" data-id= "txtPath" />
|
||||
<div data-height="10" />
|
||||
<afx-hbox data-height="30">
|
||||
<div />
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" />
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
||||
"""
|
||||
|
||||
class VFSHandle extends SettingHandle
|
||||
constructor: (scheme, parent) ->
|
||||
super(scheme, parent)
|
||||
@mplist = @find "mplist"
|
||||
@dpath = @find "dpath"
|
||||
@ppath = @find "ppath"
|
||||
@mplist.set "buttons", [
|
||||
{
|
||||
text: "+",
|
||||
onbtclick: (e) =>
|
||||
@parent.openDialog(new VFSSettingDialog(), {
|
||||
title: "__(Add mount point)"
|
||||
})
|
||||
.then (d) =>
|
||||
@parent.systemsetting.VFS.mountpoints.push {
|
||||
text: d.name, path: d.path, iconclass: "fa fa-folder", type: "fs"
|
||||
}
|
||||
@refresh()
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
onbtclick: (e) =>
|
||||
item = @mplist.get "selectedItem"
|
||||
return unless item
|
||||
selidx = $(item).index()
|
||||
@parent.openDialog("YesNoDialog", {
|
||||
title: "__(Remove)",
|
||||
text: __("Remove: {0}?", item.get("data").text)
|
||||
}).then (d) =>
|
||||
return unless d
|
||||
@parent.systemsetting.VFS.mountpoints.splice selidx, 1
|
||||
@refresh()
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
iconclass: "fa fa-pencil",
|
||||
onbtclick: (e) =>
|
||||
sel = @mplist.get "selectedItem"
|
||||
return unless sel
|
||||
@parent.openDialog(new VFSSettingDialog(), {
|
||||
title: "__(Edit mount point)",
|
||||
text: sel.get("data").text,
|
||||
path: sel.get("data").path
|
||||
}).then (d) =>
|
||||
sel.get("data").text = d.name
|
||||
sel.get("data").path = d.path
|
||||
@refresh()
|
||||
}
|
||||
]
|
||||
(@find "btndpath").set 'onbtclick', (e) =>
|
||||
@parent.openDialog("FileDialog", {
|
||||
title: "__(Select a directory)",
|
||||
mimes: ["dir"],
|
||||
hidden: true
|
||||
}).then (d) =>
|
||||
@parent.systemsetting.desktop.path = d.file.path
|
||||
@parent._gui.refreshDesktop()
|
||||
@refresh()
|
||||
|
||||
(@find "btnppath").set 'onbtclick', (e) =>
|
||||
@parent.openDialog("FileDialog", {
|
||||
title: "__(Select a directory)",
|
||||
mimes: ["dir"],
|
||||
hidden: true
|
||||
}).then (d) =>
|
||||
@parent.systemsetting.system.pkgpaths.user = d.file.path
|
||||
@refresh()
|
||||
@refresh()
|
||||
|
||||
refresh: () ->
|
||||
@mplist.set "data", @parent.systemsetting.VFS.mountpoints
|
||||
@dpath.set "text", @parent.systemsetting.desktop.path
|
||||
@ppath.set "text", @parent.systemsetting.system.pkgpaths.user
|
173
src/packages/Setting/VFSHandle.js
Normal file
173
src/packages/Setting/VFSHandle.js
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class VFSSettingDialog extends this.OS.GUI.BasicDialog {
|
||||
constructor() {
|
||||
super("VFSSettingDialog", VFSSettingDialog.scheme);
|
||||
}
|
||||
|
||||
main() {
|
||||
super.main();
|
||||
$(this.find("txtPath")).click(e => {
|
||||
return this.openDialog("FileDialog", {
|
||||
title: "__(Select a directory)",
|
||||
mimes: ["dir"],
|
||||
hidden: true
|
||||
})
|
||||
.then(d => {
|
||||
return (this.find("txtPath")).value = d.file.path;
|
||||
});
|
||||
});
|
||||
|
||||
this.find("btnOk").set("onbtclick", e => {
|
||||
const data = {
|
||||
path: (this.find("txtPath")).value,
|
||||
name: (this.find("txtName")).value
|
||||
};
|
||||
if (!data.name || (data.name === "")) { return this.error(__("Please enter mount point name")); }
|
||||
if (!data.path || (data.path === "")) { return this.error(__("Please select a directory")); }
|
||||
if (this.handle) { this.handle(data); }
|
||||
return this.quit();
|
||||
});
|
||||
|
||||
(this.find("btnCancel")).set("onbtclick", e => {
|
||||
return this.quit();
|
||||
});
|
||||
|
||||
if (!this.data) { return; }
|
||||
if (this.data.text) { (this.find("txtName")).value = this.data.text; }
|
||||
if (this.data.path) { return (this.find("txtPath")).value = this.data.path; }
|
||||
}
|
||||
}
|
||||
|
||||
VFSSettingDialog.scheme = `\
|
||||
<afx-app-window width='250' height='180' apptitle = "__(Mount Points)">
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" />
|
||||
<afx-vbox>
|
||||
<div data-height="10" />
|
||||
<afx-label data-height="30" text = "__(Name)" />
|
||||
<input type = "text" data-id= "txtName" />
|
||||
<div data-height="3" />
|
||||
<afx-label data-height="30" text = "__(Path)" />
|
||||
<input type = "text" data-id= "txtPath" />
|
||||
<div data-height="10" />
|
||||
<afx-hbox data-height="30">
|
||||
<div />
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" />
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
|
||||
class VFSHandle extends SettingHandle {
|
||||
constructor(scheme, parent) {
|
||||
super(scheme, parent);
|
||||
this.mplist = this.find("mplist");
|
||||
this.dpath = this.find("dpath");
|
||||
this.ppath = this.find("ppath");
|
||||
this.mplist.set("buttons", [
|
||||
{
|
||||
text: "+",
|
||||
onbtclick: e => {
|
||||
return this.parent.openDialog(new VFSSettingDialog(), {
|
||||
title: "__(Add mount point)"
|
||||
})
|
||||
.then(d => {
|
||||
this.parent.systemsetting.VFS.mountpoints.push({
|
||||
text: d.name, path: d.path, iconclass: "fa fa-folder", type: "fs"
|
||||
});
|
||||
return this.refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
onbtclick: e => {
|
||||
const item = this.mplist.get("selectedItem");
|
||||
if (!item) { return; }
|
||||
const selidx = $(item).index();
|
||||
return this.parent.openDialog("YesNoDialog", {
|
||||
title: "__(Remove)",
|
||||
text: __("Remove: {0}?", item.get("data").text)
|
||||
}).then(d => {
|
||||
if (!d) { return; }
|
||||
this.parent.systemsetting.VFS.mountpoints.splice(selidx, 1);
|
||||
return this.refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
text: "",
|
||||
iconclass: "fa fa-pencil",
|
||||
onbtclick: e => {
|
||||
const sel = this.mplist.get("selectedItem");
|
||||
if (!sel) { return; }
|
||||
return this.parent.openDialog(new VFSSettingDialog(), {
|
||||
title: "__(Edit mount point)",
|
||||
text: sel.get("data").text,
|
||||
path: sel.get("data").path
|
||||
}).then(d => {
|
||||
sel.get("data").text = d.name;
|
||||
sel.get("data").path = d.path;
|
||||
return this.refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
]);
|
||||
(this.find("btndpath")).set('onbtclick', e => {
|
||||
return this.parent.openDialog("FileDialog", {
|
||||
title: "__(Select a directory)",
|
||||
mimes: ["dir"],
|
||||
hidden: true
|
||||
}).then(d => {
|
||||
this.parent.systemsetting.desktop.path = d.file.path;
|
||||
this.parent._gui.refreshDesktop();
|
||||
return this.refresh();
|
||||
});
|
||||
});
|
||||
|
||||
(this.find("btnppath")).set('onbtclick', e => {
|
||||
return this.parent.openDialog("FileDialog", {
|
||||
title: "__(Select a directory)",
|
||||
mimes: ["dir"],
|
||||
hidden: true
|
||||
}).then(d => {
|
||||
this.parent.systemsetting.system.pkgpaths.user = d.file.path;
|
||||
return this.refresh();
|
||||
});
|
||||
});
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.mplist.set("data", this.parent.systemsetting.VFS.mountpoints);
|
||||
this.dpath.set("text", this.parent.systemsetting.desktop.path);
|
||||
return this.ppath.set("text", this.parent.systemsetting.system.pkgpaths.user);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class SettingHandle
|
||||
constructor: (@scheme, @parent) ->
|
||||
|
||||
find: (id) -> ($ "[data-id='#{id}']", @scheme)[0] if @scheme
|
||||
|
||||
render: () ->
|
||||
|
||||
class Setting extends this.OS.GUI.BaseApplication
|
||||
constructor: (args) ->
|
||||
super "Setting", args
|
||||
|
||||
main: () ->
|
||||
@container = @find "container"
|
||||
|
||||
new AppearanceHandle @find("appearance"), @
|
||||
new VFSHandle @find("vfs"), @
|
||||
new LocaleHandle @find("locale"), @
|
||||
new StartupHandle @find("startup"), @
|
||||
|
||||
(@find "btnsave").set "onbtclick", (e) =>
|
||||
@_api.setting()
|
||||
.then (d) =>
|
||||
return @error __("Cannot save system setting: {0}", d.error) if d.error
|
||||
@notify __("System setting saved")
|
||||
.catch (e) =>
|
||||
@error __("Cannot save system setting: {0}", e.toString()), e
|
||||
Setting.singleton = true
|
||||
this.OS.register "Setting", Setting
|
61
src/packages/Setting/main.js
Normal file
61
src/packages/Setting/main.js
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class SettingHandle {
|
||||
constructor(scheme, parent) {
|
||||
this.scheme = scheme;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
find(id) { if (this.scheme) { return ($(`[data-id='${id}']`, this.scheme))[0]; } }
|
||||
|
||||
render() {}
|
||||
}
|
||||
|
||||
class Setting extends this.OS.GUI.BaseApplication {
|
||||
constructor(args) {
|
||||
super("Setting", args);
|
||||
}
|
||||
|
||||
main() {
|
||||
this.container = this.find("container");
|
||||
|
||||
new AppearanceHandle(this.find("appearance"), this);
|
||||
new VFSHandle(this.find("vfs"), this);
|
||||
new LocaleHandle(this.find("locale"), this);
|
||||
new StartupHandle(this.find("startup"), this);
|
||||
|
||||
return (this.find("btnsave")).set("onbtclick", e => {
|
||||
return this._api.setting()
|
||||
.then(d => {
|
||||
if (d.error) { return this.error(__("Cannot save system setting: {0}", d.error)); }
|
||||
return this.notify(__("System setting saved"));
|
||||
}).catch(e => {
|
||||
return this.error(__("Cannot save system setting: {0}", e.toString()), e);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Setting.singleton = true;
|
||||
this.OS.register("Setting", Setting);
|
@ -1,41 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
class Calendar extends this.OS.GUI.BaseService
|
||||
constructor: (args) ->
|
||||
super "Calendar", args
|
||||
#@iconclass = "fa fa-commenting"
|
||||
@text = ""
|
||||
@iconclass = "fa fa-calendar"
|
||||
init: ->
|
||||
#update time each second
|
||||
@watch 1000, () =>
|
||||
now = new Date
|
||||
@text = now.toString()
|
||||
@domel.set "text", @text
|
||||
|
||||
|
||||
awake: (e) ->
|
||||
@.openDialog("CalendarDialog" )
|
||||
.then (d) ->
|
||||
console.log d
|
||||
# do nothing
|
||||
cleanup: (evt) ->
|
||||
console.log "cleanup for quit"
|
||||
# do nothing
|
||||
|
||||
this.OS.register "Calendar", Calendar
|
52
src/packages/Syslog/Calendar.js
Normal file
52
src/packages/Syslog/Calendar.js
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
class Calendar extends this.OS.GUI.BaseService {
|
||||
constructor(args) {
|
||||
super("Calendar", args);
|
||||
//@iconclass = "fa fa-commenting"
|
||||
this.text = "";
|
||||
this.iconclass = "fa fa-calendar";
|
||||
}
|
||||
init() {
|
||||
//update time each second
|
||||
return this.watch(1000, () => {
|
||||
const now = new Date;
|
||||
this.text = now.toString();
|
||||
return this.domel.set("text", this.text);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
awake(e) {
|
||||
return this.openDialog("CalendarDialog" )
|
||||
.then(d => console.log(d));
|
||||
}
|
||||
// do nothing
|
||||
cleanup(evt) {
|
||||
return console.log("cleanup for quit");
|
||||
}
|
||||
}
|
||||
// do nothing
|
||||
|
||||
this.OS.register("Calendar", Calendar);
|
@ -1,6 +1,6 @@
|
||||
coffee_files = Calendar.coffee PushNotification.coffee Syslog.coffee
|
||||
module_files = Calendar.js PushNotification.js Syslog.js
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
|
@ -1,149 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class PushNotification extends this.OS.GUI.BaseService
|
||||
constructor: (args) ->
|
||||
super "PushNotification", args
|
||||
@iconclass = "fa fa-bars"
|
||||
@cb = undefined
|
||||
@pending = []
|
||||
@logs = []
|
||||
@logmon = undefined
|
||||
init: ->
|
||||
@view = false
|
||||
@_gui.htmlToScheme PushNotification.scheme, @, @host
|
||||
|
||||
spin: (b) ->
|
||||
if b and @iconclass is "fa fa-bars"
|
||||
@iconclass = "fa fa-spinner fa-spin"
|
||||
@update()
|
||||
$(@_gui.workspace).css "cursor", "wait"
|
||||
else if not b and @iconclass is "fa fa-spinner fa-spin"
|
||||
@iconclass = "fa fa-bars"
|
||||
@update()
|
||||
$(@_gui.workspace).css "cursor", "auto"
|
||||
|
||||
main: ->
|
||||
@mlist = @find "notifylist"
|
||||
@mfeed = @find "notifeed"
|
||||
@nzone = @find "notifyzone"
|
||||
@fzone = @find "feedzone"
|
||||
(@find "btclear").set "onbtclick", (e) => @mlist.set "data", []
|
||||
(@find "bterrlog").set "onbtclick", (e) => @showLogReport()
|
||||
@subscribe "notification", (o) => @pushout 'INFO', o
|
||||
@subscribe "fail", (o) => @pushout 'FAIL', o
|
||||
@subscribe "error", (o) => @pushout 'ERROR', o
|
||||
@subscribe "info", (o) => @pushout 'INFO', o
|
||||
|
||||
|
||||
@subscribe "loading", (o) =>
|
||||
@pending.push o.id
|
||||
@spin true
|
||||
|
||||
@subscribe "loaded", (o) =>
|
||||
i = @pending.indexOf o.id
|
||||
@pending.splice i, 1 if i >= 0
|
||||
@spin false if @pending.length is 0
|
||||
|
||||
@nzone.set "height", "100%"
|
||||
@fzone.set "height", "100%"
|
||||
|
||||
($ @nzone).css "right", 0
|
||||
.css "top", "0"
|
||||
.css "bottom", "0"
|
||||
.hide()
|
||||
($ @fzone)
|
||||
#.css("z-index", 99999)
|
||||
.css("bottom", "0")
|
||||
.css "bottom", "0"
|
||||
.hide()
|
||||
|
||||
showLogReport: () ->
|
||||
@_gui.launch "Syslog"
|
||||
|
||||
addLog: (s, o) ->
|
||||
logtime = new Date()
|
||||
log = {
|
||||
type: s,
|
||||
name: o.name,
|
||||
text: "#{o.data.m}",
|
||||
id: o.id,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
error: o.data.e,
|
||||
time: logtime,
|
||||
closable: true,
|
||||
tag: "afx-bug-list-item"
|
||||
}
|
||||
if @logmon
|
||||
@logmon.addLog log
|
||||
else
|
||||
@logs.push log
|
||||
|
||||
pushout: (s, o) ->
|
||||
d = {
|
||||
text: "[#{s}] #{o.name} (#{o.id}): #{o.data.m}",
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
closable: true
|
||||
}
|
||||
@addLog s, o unless s is "INFO"
|
||||
@mlist.unshift d
|
||||
@notifeed d
|
||||
|
||||
notifeed: (d) ->
|
||||
@mfeed.unshift d, true
|
||||
($ @fzone).show()
|
||||
timer = setTimeout () =>
|
||||
@mfeed.remove d.domel
|
||||
($ @fzone).hide() if @mfeed.get("data").length is 0
|
||||
clearTimeout timer
|
||||
, 3000
|
||||
|
||||
awake: (evt) ->
|
||||
if @view then ($ @nzone).hide() else ($ @nzone).show()
|
||||
@view = not @view
|
||||
if not @cb
|
||||
@cb = (e) =>
|
||||
if not ($ e.target).closest($ @nzone).length and not ($ e.target).closest(evt.data.item).length
|
||||
($ @nzone).hide()
|
||||
$(document).unbind "click", @cb
|
||||
@view = not @view
|
||||
if @view
|
||||
$(document).on "click", @cb
|
||||
else
|
||||
$(document).unbind "click", @cb
|
||||
|
||||
cleanup: (evt) ->
|
||||
# do nothing
|
||||
PushNotification.scheme = """
|
||||
<div>
|
||||
<afx-overlay data-id = "notifyzone" width = "250px">
|
||||
<afx-hbox data-height="30">
|
||||
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
|
||||
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button>
|
||||
</afx-hbox>
|
||||
<afx-list-view data-id="notifylist"></afx-list-view>
|
||||
</afx-overlay>
|
||||
<afx-overlay data-id = "feedzone" width = "250">
|
||||
<afx-list-view data-id = "notifeed">
|
||||
</afx-list-view>
|
||||
</afx-overlay>
|
||||
</div>
|
||||
"""
|
||||
this.OS.register "PushNotification", PushNotification
|
175
src/packages/Syslog/PushNotification.js
Normal file
175
src/packages/Syslog/PushNotification.js
Normal file
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class PushNotification extends this.OS.GUI.BaseService {
|
||||
constructor(args) {
|
||||
super("PushNotification", args);
|
||||
this.iconclass = "fa fa-bars";
|
||||
this.cb = undefined;
|
||||
this.pending = [];
|
||||
this.logs = [];
|
||||
this.logmon = undefined;
|
||||
}
|
||||
init() {
|
||||
this.view = false;
|
||||
return this._gui.htmlToScheme(PushNotification.scheme, this, this.host);
|
||||
}
|
||||
|
||||
spin(b) {
|
||||
if (b && (this.iconclass === "fa fa-bars")) {
|
||||
this.iconclass = "fa fa-spinner fa-spin";
|
||||
this.update();
|
||||
return $(this._gui.workspace).css("cursor", "wait");
|
||||
} else if (!b && (this.iconclass === "fa fa-spinner fa-spin")) {
|
||||
this.iconclass = "fa fa-bars";
|
||||
this.update();
|
||||
return $(this._gui.workspace).css("cursor", "auto");
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
this.mlist = this.find("notifylist");
|
||||
this.mfeed = this.find("notifeed");
|
||||
this.nzone = this.find("notifyzone");
|
||||
this.fzone = this.find("feedzone");
|
||||
(this.find("btclear")).set("onbtclick", e => this.mlist.set("data", []));
|
||||
(this.find("bterrlog")).set("onbtclick", e => this.showLogReport());
|
||||
this.subscribe("notification", o => this.pushout('INFO', o));
|
||||
this.subscribe("fail", o => this.pushout('FAIL', o));
|
||||
this.subscribe("error", o => this.pushout('ERROR', o));
|
||||
this.subscribe("info", o => this.pushout('INFO', o));
|
||||
|
||||
|
||||
this.subscribe("loading", o => {
|
||||
this.pending.push(o.id);
|
||||
return this.spin(true);
|
||||
});
|
||||
|
||||
this.subscribe("loaded", o => {
|
||||
const i = this.pending.indexOf(o.id);
|
||||
if (i >= 0) { this.pending.splice(i, 1); }
|
||||
if (this.pending.length === 0) { return this.spin(false); }
|
||||
});
|
||||
|
||||
this.nzone.set("height", "100%");
|
||||
this.fzone.set("height", "100%");
|
||||
|
||||
($(this.nzone)).css("right", 0)
|
||||
.css("top", "0")
|
||||
.css("bottom", "0")
|
||||
.hide();
|
||||
return ($(this.fzone))
|
||||
//.css("z-index", 99999)
|
||||
.css("bottom", "0")
|
||||
.css("bottom", "0")
|
||||
.hide();
|
||||
}
|
||||
|
||||
showLogReport() {
|
||||
return this._gui.launch("Syslog");
|
||||
}
|
||||
|
||||
addLog(s, o) {
|
||||
const logtime = new Date();
|
||||
const log = {
|
||||
type: s,
|
||||
name: o.name,
|
||||
text: `${o.data.m}`,
|
||||
id: o.id,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
error: o.data.e,
|
||||
time: logtime,
|
||||
closable: true,
|
||||
tag: "afx-bug-list-item"
|
||||
};
|
||||
if (this.logmon) {
|
||||
return this.logmon.addLog(log);
|
||||
} else {
|
||||
return this.logs.push(log);
|
||||
}
|
||||
}
|
||||
|
||||
pushout(s, o) {
|
||||
const d = {
|
||||
text: `[${s}] ${o.name} (${o.id}): ${o.data.m}`,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
closable: true
|
||||
};
|
||||
if (s !== "INFO") { this.addLog(s, o); }
|
||||
this.mlist.unshift(d);
|
||||
return this.notifeed(d);
|
||||
}
|
||||
|
||||
notifeed(d) {
|
||||
let timer;
|
||||
this.mfeed.unshift(d, true);
|
||||
($(this.fzone)).show();
|
||||
return timer = setTimeout(() => {
|
||||
this.mfeed.remove(d.domel);
|
||||
if (this.mfeed.get("data").length === 0) { ($(this.fzone)).hide(); }
|
||||
return clearTimeout(timer);
|
||||
}
|
||||
, 3000);
|
||||
}
|
||||
|
||||
awake(evt) {
|
||||
if (this.view) { ($(this.nzone)).hide(); } else { ($(this.nzone)).show(); }
|
||||
this.view = !this.view;
|
||||
if (!this.cb) {
|
||||
this.cb = e => {
|
||||
if (!($(e.target)).closest($(this.nzone)).length && !($(e.target)).closest(evt.data.item).length) {
|
||||
($(this.nzone)).hide();
|
||||
$(document).unbind("click", this.cb);
|
||||
return this.view = !this.view;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (this.view) {
|
||||
return $(document).on("click", this.cb);
|
||||
} else {
|
||||
return $(document).unbind("click", this.cb);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup(evt) {}
|
||||
}
|
||||
// do nothing
|
||||
PushNotification.scheme = `\
|
||||
<div>
|
||||
<afx-overlay data-id = "notifyzone" width = "250px">
|
||||
<afx-hbox data-height="30">
|
||||
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
|
||||
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button>
|
||||
</afx-hbox>
|
||||
<afx-list-view data-id="notifylist"></afx-list-view>
|
||||
</afx-overlay>
|
||||
<afx-overlay data-id = "feedzone" width = "250">
|
||||
<afx-list-view data-id = "notifeed">
|
||||
</afx-list-view>
|
||||
</afx-overlay>
|
||||
</div>\
|
||||
`;
|
||||
this.OS.register("PushNotification", PushNotification);
|
@ -1,105 +0,0 @@
|
||||
Ant = this
|
||||
|
||||
class BugListItemTag extends this.OS.GUI.tag["afx-list-item-proto"]
|
||||
constructor: (r, o) ->
|
||||
super r, o
|
||||
|
||||
__data__: (v) ->
|
||||
return unless v
|
||||
@refs.error.set "text", v.text
|
||||
@refs.time.set "text", v.time
|
||||
@refs.error.set "icon", v.icon if v.icon
|
||||
if not v.icon
|
||||
@refs.error.set "iconclass", if v.iconclass then v.iconclass else "fa fa-bug"
|
||||
@set "closable", v.closable
|
||||
|
||||
__selected: (v) ->
|
||||
@get("data").selected = v
|
||||
|
||||
|
||||
itemlayout: () ->
|
||||
{ el: "div", children: [
|
||||
{ el: "afx-label", ref: "error", class: "afx-bug-list-item-error" },
|
||||
{ el: "afx-label", ref: "time", class: "afx-bug-list-item-time" }
|
||||
|
||||
] }
|
||||
|
||||
|
||||
this.OS.GUI.define "afx-bug-list-item", BugListItemTag
|
||||
|
||||
class Syslog extends this.OS.GUI.BaseApplication
|
||||
constructor: (args) ->
|
||||
super "Syslog", args
|
||||
|
||||
main: () ->
|
||||
@loglist = @find "loglist"
|
||||
@logdetail = @find "logdetail"
|
||||
|
||||
@_gui.pushService "Syslog/PushNotification"
|
||||
.then (srv) =>
|
||||
@srv = srv
|
||||
@loglist.set "data", @srv.logs if @srv and @srv.logs
|
||||
@srv.logmon = @
|
||||
.catch (e) =>
|
||||
@error __("Unable to load push notification service"), e
|
||||
@quit()
|
||||
|
||||
$(@find("txturi")).val Ant.OS.setting.system.error_report
|
||||
@loglist.set "onlistselect", (e) =>
|
||||
data = e.data.item.get("data") if e and e.data
|
||||
return unless data
|
||||
stacktrace = "None"
|
||||
stacktrace = data.error.stack if data.error
|
||||
$(@logdetail).text Syslog.template.format(
|
||||
data.text,
|
||||
data.type,
|
||||
data.time,
|
||||
data.name,
|
||||
data.id,
|
||||
stacktrace
|
||||
)
|
||||
@loglist.set "onitemclose", (e) =>
|
||||
el = e.data.item if e and e.data
|
||||
return true unless el
|
||||
data = el.get "data"
|
||||
console.log data
|
||||
return true unless data.selected
|
||||
$(@logdetail).text("")
|
||||
return true
|
||||
|
||||
@find("btnreport").set "onbtclick", (e) =>
|
||||
uri = $(@find("txturi")).val()
|
||||
return if uri is ""
|
||||
el = @loglist.get "selectedItem"
|
||||
return unless el
|
||||
data = el.get("data")
|
||||
return unless data
|
||||
Ant.OS.API.post uri, data
|
||||
.then (d) =>
|
||||
@notify __("Error reported")
|
||||
.catch (e) =>
|
||||
@notify __("Unable to report error: {0}", e.toString())
|
||||
|
||||
@find("btclean").set "onbtclick", (e) =>
|
||||
return unless @srv
|
||||
@srv.logs = []
|
||||
@loglist.set "data", @srv.logs
|
||||
$(@logdetail).text("")
|
||||
|
||||
addLog: (log) ->
|
||||
@loglist.push log
|
||||
|
||||
cleanup: () ->
|
||||
@srv.logmon = undefined if @srv
|
||||
|
||||
Syslog.template = """
|
||||
{0}
|
||||
Log type: {1}
|
||||
Log time: {2}
|
||||
Process: {3} ({4})
|
||||
detail:
|
||||
|
||||
{5}
|
||||
"""
|
||||
Syslog.singleton = true
|
||||
this.OS.register "Syslog", Syslog
|
132
src/packages/Syslog/Syslog.js
Normal file
132
src/packages/Syslog/Syslog.js
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS208: Avoid top-level this
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
Ant = this
|
||||
|
||||
class BugListItemTag extends this.OS.GUI.tag["afx-list-item-proto"] {
|
||||
constructor(r, o) {
|
||||
super(r, o);
|
||||
}
|
||||
|
||||
__data__(v) {
|
||||
if (!v) { return; }
|
||||
this.refs.error.set("text", v.text);
|
||||
this.refs.time.set("text", v.time);
|
||||
if (v.icon) { this.refs.error.set("icon", v.icon); }
|
||||
if (!v.icon) {
|
||||
this.refs.error.set("iconclass", v.iconclass ? v.iconclass : "fa fa-bug");
|
||||
}
|
||||
return this.set("closable", v.closable);
|
||||
}
|
||||
|
||||
__selected(v) {
|
||||
return this.get("data").selected = v;
|
||||
}
|
||||
|
||||
|
||||
itemlayout() {
|
||||
return {
|
||||
el: "div", children: [
|
||||
{ el: "afx-label", ref: "error", class: "afx-bug-list-item-error" },
|
||||
{ el: "afx-label", ref: "time", class: "afx-bug-list-item-time" }
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.OS.GUI.define("afx-bug-list-item", BugListItemTag);
|
||||
|
||||
class Syslog extends this.OS.GUI.BaseApplication {
|
||||
constructor(args) {
|
||||
super("Syslog", args);
|
||||
}
|
||||
|
||||
main() {
|
||||
this.loglist = this.find("loglist");
|
||||
this.logdetail = this.find("logdetail");
|
||||
|
||||
this._gui.pushService("Syslog/PushNotification")
|
||||
.then(srv => {
|
||||
this.srv = srv;
|
||||
if (this.srv && this.srv.logs) { this.loglist.set("data", this.srv.logs); }
|
||||
return this.srv.logmon = this;
|
||||
}).catch(e => {
|
||||
this.error(__("Unable to load push notification service"), e);
|
||||
return this.quit();
|
||||
});
|
||||
|
||||
$(this.find("txturi")).val(Ant.OS.setting.system.error_report);
|
||||
this.loglist.set("onlistselect", e => {
|
||||
let data;
|
||||
if (e && e.data) { data = e.data.item.get("data"); }
|
||||
if (!data) { return; }
|
||||
let stacktrace = "None";
|
||||
if (data.error) { stacktrace = data.error.stack; }
|
||||
return $(this.logdetail).text(Syslog.template.format(
|
||||
data.text,
|
||||
data.type,
|
||||
data.time,
|
||||
data.name,
|
||||
data.id,
|
||||
stacktrace
|
||||
)
|
||||
);
|
||||
});
|
||||
this.loglist.set("onitemclose", e => {
|
||||
let el;
|
||||
if (e && e.data) { el = e.data.item; }
|
||||
if (!el) { return true; }
|
||||
const data = el.get("data");
|
||||
console.log(data);
|
||||
if (!data.selected) { return true; }
|
||||
$(this.logdetail).text("");
|
||||
return true;
|
||||
});
|
||||
|
||||
this.find("btnreport").set("onbtclick", e => {
|
||||
const uri = $(this.find("txturi")).val();
|
||||
if (uri === "") { return; }
|
||||
const el = this.loglist.get("selectedItem");
|
||||
if (!el) { return; }
|
||||
const data = el.get("data");
|
||||
if (!data) { return; }
|
||||
return Ant.OS.API.post(uri, data)
|
||||
.then(d => {
|
||||
return this.notify(__("Error reported"));
|
||||
}).catch(e => {
|
||||
return this.notify(__("Unable to report error: {0}", e.toString()));
|
||||
});
|
||||
});
|
||||
|
||||
return this.find("btclean").set("onbtclick", e => {
|
||||
if (!this.srv) { return; }
|
||||
this.srv.logs = [];
|
||||
this.loglist.set("data", this.srv.logs);
|
||||
return $(this.logdetail).text("");
|
||||
});
|
||||
}
|
||||
|
||||
addLog(log) {
|
||||
return this.loglist.push(log);
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if (this.srv) { return this.srv.logmon = undefined; }
|
||||
}
|
||||
}
|
||||
|
||||
Syslog.template = `\
|
||||
{0}
|
||||
Log type: {1}
|
||||
Log time: {2}
|
||||
Process: {3} ({4})
|
||||
detail:
|
||||
|
||||
{5}\
|
||||
`;
|
||||
Syslog.singleton = true;
|
||||
this.OS.register("Syslog", Syslog);
|
@ -7,20 +7,14 @@ main: title clean js css copy
|
||||
title:
|
||||
@echo "$(BLUE)======= Package $(PKG_NAME) =======$(NC)"
|
||||
|
||||
coffee:
|
||||
module:
|
||||
- mkdir build
|
||||
- [ ! -z "$(module_dir)" ] && [ -d build/$(module_dir) ] && rm -r build/$(module_dir)
|
||||
mkdir -p build/$(module_dir)
|
||||
for f in $(coffee_files); do (cat "$${f}"; echo) >>"build/main.coffee";done
|
||||
coffee --compile build/main.coffee
|
||||
- rm build/*.coffee
|
||||
[ -z "$(module_dir_src)" ] || (for f in $(module_dir_src)/*; do cp -rf "$$f" build/$(module_dir)/; done)
|
||||
[ -z "$(module_dir_src)" ] || (for f in build/$(module_dir)/*.coffee; do coffee --compile "$$f"; done)
|
||||
[ -z "$(module_dir_src)" ] || (rm build/$(module_dir)/*.coffee)
|
||||
|
||||
echo "(function() {" > "build/main.js"
|
||||
for f in $(module_files); do (cat "$${f}"; echo) >>"build/main.js";done
|
||||
echo "}).call(this);" >> "build/main.js"
|
||||
|
||||
js: coffee
|
||||
for f in $(jsfiles); do (cat "$${f}"; echo) >> build/main.js; done
|
||||
js: module
|
||||
for f in $(libfiles); do (cat "$${f}"; echo) >> build/main.js; done
|
||||
|
||||
css:
|
||||
for f in $(cssfiles); do (cat "$${f}"; echo) >> build/main.css; done
|
||||
|
Reference in New Issue
Block a user