add extension maker

This commit is contained in:
Xuan Sang LE 2020-05-17 23:58:21 +02:00
parent a29a449eaf
commit c94cb0963d
17 changed files with 512 additions and 150 deletions

View File

@ -12,10 +12,16 @@ class TreeViewItemPrototype extends Ant.OS.GUI.BaseTag
@setopt "selected", false
@setopt "treepath", @aid()
update: (p) ->
return unless p
return unless p is @get("treepath")
@set "open", true
__data__: (v) ->
return unless v
@set "nodes", v.nodes if v.nodes
@set "open", v.open
@set "treepath", v.path if v.path
__selected__: (v) ->
$(@refs.wrapper).removeClass()
@ -152,7 +158,7 @@ class TreeViewTag extends Ant.OS.GUI.BaseTag
@setopt "fetch", undefined
@setopt "treepath", @aid()
@indexcounter = 0
__selectedItem: (v) ->
return unless v
@get("selectedItem").set "selected", false if @get("selectedItem")
@ -176,6 +182,7 @@ class TreeViewTag extends Ant.OS.GUI.BaseTag
__data__: (v) ->
return unless v
$(@root).empty()
@set "treepath", v.path if v.path
el = $("<#{@get "itemtag"}>").appendTo @root
el[0].uify undefined
el[0].set "treeroot", if @is_root() then @ else @get "treeroot"

View File

@ -10,7 +10,7 @@ class Ant.OS.GUI.BaseTag
@root.get = (k) => @get k
@root.aid = () => @aid()
@root.calibrate = () => @calibrate()
@root.sync = () => @sync()
@root.sync = (d) => @sync(d)
@mounted = false
@root.setup = () => @setup()
@refs = {}
@ -67,9 +67,9 @@ class Ant.OS.GUI.BaseTag
return @opts if opt is "*"
@opts[opt]
sync: () ->
@update()
$(@root).children().each () -> @update()
sync: (d) ->
@update(d)
$(@root).children().each () -> @update(d)
@root
setup: () ->
@ -116,9 +116,9 @@ Element.prototype.mount = () ->
$(@).children().each () -> @mount()
@
Element.prototype.update = () ->
return @sync() if @sync
$(@).children().each () -> @update()
Element.prototype.update = (d) ->
return @sync(d) if @sync
$(@).children().each () -> @update(d)
@
Element.prototype.uify = (observable) ->

View File

@ -255,7 +255,8 @@ class RemoteFileHandle extends Ant.OS.API.VFS.BaseFileHandle
@b64(t)
.then (r) =>
Ant.OS.API.handle.write @path, r
.then (result) -> resolve result
.then (result) ->
resolve result
.catch (e) -> reject e
.catch (e) -> reject e

View File

@ -1,4 +1,4 @@
coffee_files = coffees/CommandPalette.coffee coffees/main.coffee
coffee_files = coffees/CommandPalette.coffee coffees/main.coffee coffees/BaseExtension.coffee
module_dir = extensions
module_dir_src = coffees/extensions

View File

@ -0,0 +1,125 @@
class CodePad.BaseExtension
constructor: (@app) ->
preload: () ->
Ant.OS.API.require @dependencies()
import: (lib) ->
Ant.OS.API.requires lib
basedir: () ->
"#{@app.meta().path}/extensions"
notify: (m) ->
@app.notify m
error: (m) ->
@app.error m
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
.catch (e) -> reject 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.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
.catch (e) -> reject e
mkar: (src, dest) ->
@notify __("Preparing for release")
new Promise (r, e) =>
@import("os://scripts/jszip.min.js").then () ->
src.asFileHandle()
.read().then (d) ->
return e d.error if d.error
r d.result
.catch (ex) -> e ex
.catch (ex) -> e ex
.then (files) =>
new Promise (r, e) =>
zip = new JSZip()
fn = (list) =>
return r zip if list.length is 0
f = (list.splice 0, 1)[0].path.asFileHandle()
return fn list if f.type is "dir"
f.read("binary").then (d) =>
zip.file f.basename, d, { binary: true }
@notify __("add {0} to zip", f.basename)
fn list
.catch (ex) -> e ex
fn files
.then (zip) =>
zip.generateAsync({ type: "base64" }).then (data) =>
dest.asFileHandle()
.setCache('data:application/zip;base64,' + data)
.write("base64").then (r) =>
return @error __("Cannot save the zip file: {0}", r.error) if r.error
@notify __("Package is generated in release folder")
.catch (e) => @error e.toString()
.catch (e) => @error e.toString()
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) =>
@mkdirAll list
.then () -> resolve()
.catch (e) -> reject e
.catch (e) -> reject 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()
.setCache(data.format name, "#{path}/#{name}")
.write "text/plain"
.then () =>
@mkfileAll list, path, name
.then () -> resolve()
.catch (e) -> reject e
.catch (e) -> reject e
.catch (e) -> reject 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 = {}

View File

@ -25,7 +25,7 @@ class App.extensions.AntOSDK extends App.BaseExtension
@mktpl dir.parent().path, dir.basename
buildnrun: () ->
@metadata().then (meta) =>
@metadata("project.json").then (meta) =>
@build(meta).then () =>
@run(meta).catch (e) => @error.toString()
.catch (e) =>
@ -33,9 +33,9 @@ class App.extensions.AntOSDK extends App.BaseExtension
.catch (e) => @error e.toString()
release: () ->
@metadata().then (meta) =>
@metadata("project.json").then (meta) =>
@build(meta).then () =>
@mkar(meta)
@mkar("#{meta.root}/build/debug", "#{meta.root}/build/release/#{meta.name}.zip")
.then () ->
.catch (e) => @error.toString()
.catch (e) =>
@ -46,7 +46,6 @@ class App.extensions.AntOSDK extends App.BaseExtension
# private functions
mktpl: (path, name, flag) ->
rpath = "#{path}/#{name}"
console.log rpath
dirs = [
"#{rpath}/build",
"#{rpath}/build/release",
@ -58,11 +57,11 @@ class App.extensions.AntOSDK extends App.BaseExtension
]
dirs.unshift rpath if flag
files = [
["main.tpl", "#{rpath}/coffees/main.coffee"],
["package.tpl", "#{rpath}/package.json"],
["project.tpl", "#{rpath}/project.json"],
["README.tpl", "#{rpath}/README.md"],
["scheme.tpl", "#{rpath}/assets/scheme.html"]
["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 () =>
@ -73,48 +72,6 @@ class App.extensions.AntOSDK extends App.BaseExtension
@app.openFile "#{rpath}/README.md".asFileHandle()
.catch (e) => @error e.stack
.catch (e) => @error e.stack
mkdirAll: (list) ->
new Promise (resolve, reject) =>
return resolve() if list.length is 0
path = (list.splice 0, 1)[0].asFileHandle()
console.log path.parent().path, path.basename
path.parent().mk path.basename
.then (d) =>
@mkdirAll list
.then () -> resolve()
.catch (e) -> reject e
.catch (e) -> reject e
mkfileAll: (list, path, name) ->
new Promise (resolve, reject) =>
return resolve() if list.length is 0
item = (list.splice 0, 1)[0]
"#{@basedir()}/AntOSDK/templates/#{item[0]}"
.asFileHandle()
.read()
.then (data) =>
file = item[1].asFileHandle()
.setCache(data.format name, "#{path}/#{name}")
.write "text/plain"
.then () =>
@mkfileAll list, path, name
.then () -> resolve()
.catch (e) -> reject e
.catch (e) -> reject e
.catch (e) -> reject e
metadata: () ->
new Promise (resolve, reject) =>
if not @app.currdir
return reject @app._api.throwe __("Project folder is not found")
"#{@app.currdir.path}/project.json"
.asFileHandle()
.read("json")
.then (data) ->
resolve data
.catch (e) =>
reject @app._api.throwe __("Unable to read project meta-data")
verify: (list) ->
new Promise (resolve, reject) =>
@ -133,7 +90,7 @@ class App.extensions.AntOSDK extends App.BaseExtension
compile: (meta) ->
new Promise (resolve, reject) =>
@import("#{@basedir()}/AntOSDK/coffeescript.js").then () =>
@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) =>
@ -192,66 +149,4 @@ class App.extensions.AntOSDK extends App.BaseExtension
@notify __("Installing...")
@app.systemsetting.system.packages[meta.name] = v
@notify __("Running {0}...", meta.name)
@app._gui.forceLaunch meta.name
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
.catch (e) -> reject 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.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
.catch (e) -> reject e
mkar: (meta) ->
@notify __("Preparing for release")
new Promise (r, e) =>
@import("os://scripts/jszip.min.js").then () ->
"#{meta.root}/build/debug".asFileHandle()
.read().then (d) ->
return e d.error if d.error
r d.result
.catch (ex) -> e ex
.catch (ex) -> e ex
.then (files) =>
new Promise (r, e) =>
zip = new JSZip()
fn = (list) =>
return r zip if list.length is 0
f = (list.splice 0, 1)[0].path.asFileHandle()
return fn list if f.type is "dir"
f.read("binary").then (d) =>
zip.file f.basename, d, { binary: true }
@notify __("add {0} to zip", f.basename)
fn list
.catch (ex) -> e ex
fn files
.then (zip) =>
zip.generateAsync({ type: "base64" }).then (data) =>
"#{meta.root}/build/release/#{meta.name}.zip"
.asFileHandle()
.setCache('data:application/zip;base64,' + data)
.write("base64").then (r) =>
return @error __("Cannot save the zip file: {0}", r.error) if r.error
@notify __("Package is generated in release folder")
.catch (e) => @error e.toString()
.catch (e) => @error e.toString()
@app._gui.forceLaunch meta.name

View File

@ -0,0 +1,217 @@
# 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.toString()
.catch (e) =>
@error e.toString()
.catch (e) => @error e.toString()
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.toString()
.catch (e) =>
@error e.toString()
.catch (e) => @error e.toString()
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 e.stack
# 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 e.stack
.catch (e) => @error e.stack
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
catch ex
reject ex
.catch (e) -> reject 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
.catch (e) -> reject e
.catch (e) -> reject 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) ->
return e d if d.error
r()
.catch (ex) -> e ex
.then () ->
new Promise (r, e) ->
"#{meta.root}/build/debug/extension.json"
.asFileHandle()
.setCache meta.meta
.write("object")
.then (data) ->
return e data.error if data.error
r data
.catch (ex) -> e ex
.then () =>
@copy ("#{meta.root}/#{v}" for v in meta.copies), "#{meta.root}/build/debug"
.then () -> resolve()
.catch (e) -> reject e
.catch (e) -> reject e
run: (meta) ->
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
.catch (e) => @error e.toString()
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
.catch (e) -> reject 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
.catch (e) -> reject e
.catch (e) -> reject 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
.catch (e) -> reject 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)
.catch (e) -> reject e
else
@installExtension files, zip
.then () -> resolve()
.catch (e) -> reject(e)
.catch (e) -> reject e
.catch (e) -> reject e
.catch (e) -> reject e

View File

@ -0,0 +1,17 @@
{
"root": "{1}",
"javascripts": [],
"coffees": ["{0}.coffee"],
"copies": [],
"meta": {
"name": "{0}",
"text": "{0}",
"version": "0.0.1-a",
"actions" : [
{
"text": "__(Example action)",
"name": "test"
}
]
}
}

View File

@ -0,0 +1,10 @@
# import the CodePad application module
App = this.OS.APP.CodePad
# define the extension
class App.extensions.{0} extends App.BaseExtension
constructor: (app) ->
super app
test: () ->
@notify "Test action is invoked"

View File

@ -92,6 +92,17 @@ class CodePad extends this.OS.GUI.BaseApplication
}]
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"
@ -221,7 +232,7 @@ class CodePad extends this.OS.GUI.BaseApplication
.then (d) =>
for ext in d
if @extensions[ext.name]
@extensions[ext.name].child = {}
@extensions[ext.name].child = []
@extensions[ext.name].addAction v for v in ext.actions
else
@extensions[ext.name] = new CMDMenu ext.text
@ -271,6 +282,86 @@ class CodePad extends this.OS.GUI.BaseApplication
@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) =>
return @error __("Fail to create {0}: {1}", d, r.error) if r.error
@fileview.update dir.path
.catch (e) =>
@error __("Fail to create: {0}", e.stack)
when "newdir"
return unless dir
@openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)"
})
.then (d) =>
dir.mk(d)
.then (r) =>
return @error __("Fail to create {0}: {1}", d, r.error) if r.error
@fileview.update dir.path
.catch (e) =>
@error __("Fail to create: {0}", e.stack)
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) =>
return @error __("Fail to rename to {0}: {1}", d, r.error) if r.error
@fileview.update dir.path
.catch (e) =>
console.log e
@error __("Fail to rename: {0}", e.stack)
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) =>
return @error __("Fail to delete {0}: {1}", file.filename, r.error) if r.error
@fileview.update dir.path
.catch (e) =>
@error __("Fail to delete: {0}", e.stack)
else
save: (file) ->
file.write("text/plain")
.then (d) =>
@ -352,28 +443,6 @@ class CodePad extends this.OS.GUI.BaseApplication
]
menu
class CodePad.BaseExtension
constructor: (@app) ->
preload: () ->
Ant.OS.API.require @dependencies()
import: (lib) ->
Ant.OS.API.requires lib
basedir: () ->
"#{@app.meta().path}/extensions"
notify: (m) ->
@app.notify m
error: (m) ->
@app.error m
dependencies: () ->
[]
class CMDMenu
constructor: (@text, @shortcut) ->
@child = []
@ -411,8 +480,6 @@ CMDMenu.fromMenu = (mn) ->
CodePad.CMDMenu = CMDMenu
CodePad.extensions = {}
CodePad.dependencies = [
"os://scripts/ace/ace.js",
"os://scripts/ace/ext-language_tools.js",

View File

@ -21,5 +21,28 @@
"name": "release"
}
]
},
{
"name": "ExtensionMaker",
"text": "CodePad Extension",
"version": "0.0.1-a",
"actions" : [
{
"text": "__(New Extension)",
"name": "create"
},
{
"text": "__(Build and Run)",
"name": "buildnrun"
},
{
"text": "__(Build release)",
"name": "release"
},
{
"text": "__(Install extension)",
"name": "install"
}
]
}
]