mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-04-16 21:06:44 +02:00
480 lines
19 KiB
CoffeeScript
480 lines
19 KiB
CoffeeScript
# 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/.
|
|
|
|
Ant.OS.GUI =
|
|
subwindows: new Object()
|
|
dialog: undefined
|
|
fullscreen: false
|
|
workspace: "#desktop"
|
|
shortcut:
|
|
ALT: {}
|
|
CTRL: {}
|
|
SHIFT: {}
|
|
META: {}
|
|
htmlToScheme: (html, app, parent) ->
|
|
scheme = $.parseHTML html
|
|
|
|
$(app.scheme).remove() if app.scheme
|
|
($ parent).append scheme
|
|
app.scheme = scheme[0]
|
|
scheme[0].uify app.observable
|
|
app.main()
|
|
app.show()
|
|
loadScheme: (path, app, parent) ->
|
|
path.asFileHandle().read()
|
|
.then (x) ->
|
|
return null unless x
|
|
Ant.OS.GUI.htmlToScheme x, app, parent
|
|
.catch (e) ->
|
|
Ant.OS.announcer.oserror __("Cannot load scheme: {0}", path), e
|
|
clearTheme: () ->
|
|
$ "head link#ostheme"
|
|
.attr "href", ""
|
|
|
|
loadTheme: (name, force) ->
|
|
Ant.OS.GUI.clearTheme() if force
|
|
path = "resources/themes/#{name}/#{name}.css"
|
|
$ "head link#ostheme"
|
|
.attr "href", path
|
|
|
|
pushServices: (srvs) ->
|
|
return unless srvs.length > 0
|
|
Ant.OS.announcer.observable.one "srvroutineready", () ->
|
|
srvs.splice 0, 1
|
|
Ant.OS.GUI.pushServices srvs
|
|
Ant.OS.GUI.pushService srvs[0]
|
|
|
|
openDialog: (d, data) ->
|
|
new Promise (resolve, reject) ->
|
|
if Ant.OS.GUI.dialog
|
|
Ant.OS.GUI.dialog.show()
|
|
return resolve()
|
|
if not Ant.OS.GUI.subwindows[d]
|
|
ex = Ant.OS.API.throwe "Dialog"
|
|
return reject(ex)
|
|
Ant.OS.GUI.dialog = new Ant.OS.GUI.subwindows[d]()
|
|
Ant.OS.GUI.dialog.parent = Ant.OS.GUI
|
|
Ant.OS.GUI.dialog.handle = resolve
|
|
Ant.OS.GUI.dialog.reject = reject
|
|
Ant.OS.GUI.dialog.pid = -1
|
|
Ant.OS.GUI.dialog.data = data
|
|
Ant.OS.GUI.dialog.init()
|
|
|
|
pushService: (ph) ->
|
|
arr = ph.split "/"
|
|
srv = arr[1]
|
|
app = arr[0]
|
|
return Ant.OS.PM.createProcess srv, Ant.OS.APP[srv] if Ant.OS.APP[srv]
|
|
Ant.OS.GUI.loadApp app
|
|
.then (a) ->
|
|
return Ant.OS.PM.createProcess srv, Ant.OS.APP[srv] if Ant.OS.APP[srv]
|
|
.catch (e) ->
|
|
Ant.OS.announcer.trigger "srvroutineready", srv
|
|
Ant.OS.announcer.osfail __("Cannot read service script: {0}", srv), e
|
|
|
|
appsByMime: (mime) ->
|
|
metas = ( v for k, v of Ant.OS.setting.system.packages when v and v.app )
|
|
mimes = ( m.mimes for m in metas when m)
|
|
apps = []
|
|
# search app by mimes
|
|
f = ( arr, idx ) ->
|
|
try
|
|
arr.filter (m, i) ->
|
|
if mime.match (new RegExp m, "g")
|
|
return false if (apps.indexOf metas[idx]) >= 0
|
|
apps.push metas[idx]
|
|
return false
|
|
return false
|
|
catch e
|
|
Ant.OS.announcer.osfail __("Error find app by mimes {0}", mime), e
|
|
|
|
( f m, i if m ) for m, i in mimes
|
|
return apps
|
|
|
|
appsWithServices: () ->
|
|
o = {}
|
|
o[k] = v for k, v of Ant.OS.setting.system.packages when v and v.services and v.services.length > 0
|
|
o
|
|
|
|
openWith: (it) ->
|
|
return unless it
|
|
return Ant.OS.GUI.launch it.app if it.type is "app" and it.app
|
|
return Ant.OS.announcer.osinfo __("Application {0} is not executable", it.text) if it.type is "app"
|
|
apps = Ant.OS.GUI.appsByMime ( if it.type is "dir" then "dir" else it.mime )
|
|
return Ant.OS.announcer.osinfo __("No application available to open {0}", it.filename) if apps.length is 0
|
|
return Ant.OS.GUI.launch apps[0].app, [it.path] if apps.length is 1
|
|
list = ( { text: e.app, icon: e.icon, iconclass: e.iconclass } for e in apps )
|
|
Ant.OS.GUI.openDialog("SelectionDialog", {
|
|
title: __("Open with"),
|
|
data: list
|
|
}).then ( d ) ->
|
|
Ant.OS.GUI.launch d.text, [ { path: it.path, type: it.type }]
|
|
|
|
forceLaunch: (app, args) ->
|
|
console.warn "This method is used for developing only, please use the launch method instead"
|
|
Ant.OS.GUI.unloadApp app
|
|
Ant.OS.GUI.launch app, args
|
|
|
|
unloadApp: (app) ->
|
|
Ant.OS.PM.killAll app, true
|
|
($ Ant.OS.APP[app].style).remove() if Ant.OS.APP[app] and Ant.OS.APP[app].style
|
|
delete Ant.OS.APP[app]
|
|
|
|
loadApp: (app) ->
|
|
new Promise (resolve, reject) ->
|
|
path = Ant.OS.setting.system.packages[app].path if Ant.OS.setting.system.packages[app].path
|
|
js = path + "/main.js"
|
|
|
|
js.asFileHandle().read("script")
|
|
.then (d) ->
|
|
# load app meta data
|
|
"#{path}/package.json".asFileHandle().read("json")
|
|
.then (data) ->
|
|
data.path = path
|
|
Ant.OS.APP[app].meta = data if Ant.OS.APP[app]
|
|
Ant.OS.APP[v].meta = data for v in data.services if data.services
|
|
#load css file
|
|
css = "#{path}/main.css"
|
|
css.asFileHandle().onready()
|
|
.then (d) ->
|
|
stamp = (new Date).timestamp()
|
|
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{Ant.OS.API.handle.get}/#{css}?stamp=#{stamp}" }
|
|
.appendTo 'head'
|
|
Ant.OS.APP[app].style = el[0] if Ant.OS.APP[app]
|
|
resolve app
|
|
.catch (e) -> resolve app
|
|
.catch (e) -> reject e
|
|
#ok app
|
|
.catch (e) -> reject e
|
|
launch: (app, args) ->
|
|
if not Ant.OS.APP[app]
|
|
# first load it
|
|
Ant.OS.GUI.loadApp(app).then (a) ->
|
|
Ant.OS.PM.createProcess a, Ant.OS.APP[a], args
|
|
else
|
|
# now launch it
|
|
if Ant.OS.APP[app]
|
|
Ant.OS.PM.createProcess app, Ant.OS.APP[app], args
|
|
|
|
dock: (app, meta) ->
|
|
# dock an application to a dock
|
|
# create a data object
|
|
data =
|
|
icon: null
|
|
iconclass: meta.iconclass || ""
|
|
app: app
|
|
onbtclick: () -> app.toggle()
|
|
# TODO: this path is not good, need to create a blob of it
|
|
data.icon = "#{meta.path}/#{meta.icon}" if meta.icon
|
|
# TODO: add default app icon class in system setting
|
|
# so that it can be themed
|
|
data.iconclass = "fa fa-cogs" if (not meta.icon) and (not meta.iconclass)
|
|
dock = $ "#sysdock"
|
|
app.init()
|
|
app.one "rendered", () ->
|
|
dock.get(0).newapp data
|
|
app.sysdock = dock.get(0)
|
|
app.appmenu = ($ "[data-id = 'appmenu']", "#syspanel")[0]
|
|
app.subscribe "systemlocalechange", (name) ->
|
|
app.updateLocale(name)
|
|
app.update()
|
|
app.subscribe "appregistry", ( m ) ->
|
|
app.applySetting m.data.m if (m.name is app.name)
|
|
|
|
toggleFullscreen: () ->
|
|
el = document.documentElement
|
|
if Ant.OS.GUI.fullscreen
|
|
return document.exitFullscreen() if document.exitFullscreen
|
|
return document.mozCancelFullScreen() if document.mozCancelFullScreen
|
|
return document.webkitExitFullscreen() if document.webkitExitFullscreen
|
|
return document.cancelFullScreen() if document.cancelFullScreen
|
|
else
|
|
return el.requestFullscreen() if el.requestFullscreen
|
|
return el.mozRequestFullScreen() if el.mozRequestFullScreen
|
|
return el.webkitRequestFullscreen() if el.webkitRequestFullscreen
|
|
return el.msRequestFullscreen() if el.msRequestFullscreen
|
|
|
|
undock: (app) ->
|
|
($ "#sysdock").get(0).removeapp app
|
|
|
|
attachservice: (srv) ->
|
|
($ "#syspanel")[0].attachservice srv
|
|
srv.init()
|
|
srv.subscribe "systemlocalechange", (name) -> srv.update()
|
|
detachservice: (srv) ->
|
|
($ "#syspanel")[0].detachservice srv
|
|
bindContextMenu: (event) ->
|
|
handle = (e) ->
|
|
if e.contextmenuHandle
|
|
e.contextmenuHandle event, ($ "#contextmenu")[0]
|
|
else
|
|
p = $(e).parent().get(0)
|
|
handle p if p isnt ($ "#workspace").get(0)
|
|
handle event.target
|
|
event.preventDefault()
|
|
|
|
bindKey: (k, f) ->
|
|
arr = k.split "-"
|
|
return unless arr.length is 2
|
|
fnk = arr[0].toUpperCase()
|
|
c = arr[1].toUpperCase()
|
|
return unless Ant.OS.GUI.shortcut[fnk]
|
|
Ant.OS.GUI.shortcut[fnk][c] = f
|
|
|
|
wallpaper: (obj) ->
|
|
if obj
|
|
Ant.OS.setting.appearance.wp = obj
|
|
wp = Ant.OS.setting.appearance.wp
|
|
$("body").css("background-image", "url(#{Ant.OS.API.handle.get}/#{wp.url})" )
|
|
.css("background-size", wp.size)
|
|
.css("background-repeat", wp.repeat)
|
|
|
|
showTooltip: (el, text, e) ->
|
|
el = el[0]
|
|
label = ($ "#systooltip")[0]
|
|
cb = (ev) ->
|
|
if $(ev.target).closest(el).length is 0
|
|
$(label).hide()
|
|
$(document).off "mousemove", cb
|
|
$(document).on "mousemove", cb
|
|
arr = text.split /:(.+)/
|
|
tip = text
|
|
tip = arr[1] if arr.length > 1
|
|
offset = $(el).offset()
|
|
w = $(el).width()
|
|
h = $(el).height()
|
|
label.set "text", tip
|
|
$(label).show()
|
|
switch arr[0]
|
|
when "cr" # center right of the element
|
|
left = offset.left + w + 5
|
|
top = offset.top + h / 2 - $(label).height() / 2
|
|
when "ct" #ceter top
|
|
left = offset.left + w / 2 - $(label).width() / 2
|
|
top = offset.top - $(label).height() - 5
|
|
else
|
|
return unless e
|
|
top = e.clientY + 5
|
|
left = e.clientX + 5
|
|
$(label).css "top", top + "px"
|
|
.css "left", left + "px"
|
|
|
|
initDM: ->
|
|
scheme = $.parseHTML Ant.OS.GUI.schemes.ws
|
|
($ "#wrapper").append scheme
|
|
|
|
Ant.OS.announcer.observable.one "sysdockloaded", () ->
|
|
($ window).bind 'keydown', (event) ->
|
|
dock = ($ "#sysdock")[0]
|
|
return unless dock
|
|
app = dock.get "selectedApp"
|
|
#return true unless app
|
|
c = String.fromCharCode(event.which).toUpperCase()
|
|
fnk = undefined
|
|
if event.ctrlKey
|
|
fnk = "CTRL"
|
|
else if event.metaKey
|
|
fnk = "META"
|
|
else if event.shiftKey
|
|
fnk = "SHIFT"
|
|
else if event.altKey
|
|
fnk = "ALT"
|
|
|
|
return unless fnk
|
|
r = if app then app.shortcut fnk, c, event else true
|
|
return event.preventDefault() if not r
|
|
return unless Ant.OS.GUI.shortcut[fnk]
|
|
return unless Ant.OS.GUI.shortcut[fnk][c]
|
|
Ant.OS.GUI.shortcut[fnk][c](event)
|
|
event.preventDefault()
|
|
# system menu and dock
|
|
$("#syspanel")[0].uify()
|
|
$("#sysdock")[0].uify()
|
|
$("#systooltip")[0].uify()
|
|
$("#contextmenu")[0].uify()
|
|
|
|
($ "#workspace").contextmenu (e) -> Ant.OS.GUI.bindContextMenu e
|
|
# tooltip
|
|
($ document).mouseover (e) ->
|
|
el = $(e.target).closest "[tooltip]"
|
|
return unless el.length > 0
|
|
Ant.OS.GUI.showTooltip el, ($(el).attr "tooltip"), e
|
|
|
|
fp = Ant.OS.setting.desktop.path.asFileHandle()
|
|
# desktop default file manager
|
|
desktop = $ Ant.OS.GUI.workspace
|
|
desktop[0].fetch = () ->
|
|
file = Ant.OS.setting.desktop.path.asFileHandle()
|
|
fn = () ->
|
|
file.read().then (d) ->
|
|
return Ant.OS.announcer.osfail d.error, (Ant.OS.API.throwe "OS.VFS"), d.error if d.error
|
|
items = []
|
|
$.each d.result, (i, v) ->
|
|
return if v.filename[0] is '.' and not Ant.OS.setting.desktop.showhidden
|
|
v.text = v.filename
|
|
#v.text = v.text.substring(0,9) + "..." ifv.text.length > 10
|
|
v.iconclass = v.type
|
|
items.push(v)
|
|
desktop[0].set "data", items
|
|
desktop[0].refresh()
|
|
|
|
file.onready()
|
|
.then () -> fn()
|
|
.catch ( e ) -> # try to create the path
|
|
console.log "#{file.path} not found"
|
|
name = file.basename
|
|
file.parent().asFileHandle().mk(name).then (r) ->
|
|
ex = Ant.OS.API.throwe "OS.VFS"
|
|
if r.error then Ant.OS.announcer.osfail d.error, ex, d.error else fn()
|
|
|
|
desktop[0].ready = (e) ->
|
|
e.observable = Ant.OS.announcer
|
|
window.onresize = () ->
|
|
Ant.OS.announcer.trigger "desktopresize"
|
|
e.refresh()
|
|
|
|
desktop[0].set "onlistselect", (d) ->
|
|
($ "#sysdock").get(0).set "selectedApp", null
|
|
|
|
desktop[0].set "onlistdbclick", ( d ) ->
|
|
($ "#sysdock").get(0).set "selectedApp", null
|
|
it = desktop[0].get "selectedItem"
|
|
Ant.OS.GUI.openWith it.get("data")
|
|
|
|
#($ "#workingenv").on "click", (e) ->
|
|
# desktop[0].set "selected", -1
|
|
|
|
desktop.on "click", (e) ->
|
|
return unless e.target.tagName.toUpperCase() is "UL"
|
|
desktop[0].unselect()
|
|
($ "#sysdock").get(0).set "selectedApp", null
|
|
|
|
desktop[0].contextmenuHandle = (e, m) ->
|
|
desktop[0].unselect() if e.target.tagName.toUpperCase() is "UL"
|
|
($ "#sysdock").get(0).set "selectedApp", null
|
|
menu = [
|
|
{ text: __("Open"), dataid: "desktop-open" },
|
|
{ text: __("Refresh"), dataid: "desktop-refresh" }
|
|
]
|
|
menu = menu.concat ( v for k, v of Ant.OS.setting.desktop.menu)
|
|
m.set "items", menu
|
|
m.set "onmenuselect", (evt) ->
|
|
item = evt.data.item.get("data")
|
|
switch item.dataid
|
|
when "desktop-open"
|
|
it = desktop[0].get "selectedItem"
|
|
return Ant.OS.GUI.openWith it.get("data") if it
|
|
it = Ant.OS.setting.desktop.path.asFileHandle()
|
|
it.mime = "dir"
|
|
it.type = "dir"
|
|
Ant.OS.GUI.openWith it
|
|
when "desktop-refresh"
|
|
desktop[0].fetch()
|
|
else
|
|
Ant.OS.GUI.launch item.app, item.args if item.app
|
|
m.show(e)
|
|
|
|
desktop[0].fetch()
|
|
Ant.OS.announcer.observable.on "VFS", (d) ->
|
|
return if ["read", "publish", "download"].includes d.data.m
|
|
desktop[0].fetch() if d.data.file.hash() is fp.hash() or d.data.file.parent().hash() is fp.hash()
|
|
Ant.OS.announcer.ostrigger "desktoploaded"
|
|
# mount it
|
|
desktop[0].uify()
|
|
|
|
refreshDesktop: () ->
|
|
($ Ant.OS.GUI.workspace)[0].fetch()
|
|
|
|
mkdialog: (conf) ->
|
|
return new Ant.OS.GUI.BasicDialog conf.name, conf.layout
|
|
|
|
login: () ->
|
|
scheme = $.parseHTML Ant.OS.GUI.schemes.login
|
|
($ "#wrapper").append scheme
|
|
($ "#btlogin").click () ->
|
|
data =
|
|
username: ($ "#txtuser").val(),
|
|
password: ($ "#txtpass").val()
|
|
Ant.OS.API.handle.login data
|
|
.then (d) ->
|
|
return ($ "#login_error").html d.error if d.error
|
|
Ant.OS.GUI.startAntOS d.result
|
|
.catch (e) ->
|
|
($ "#login_error").html "Login: server error"
|
|
($ "#txtpass").keyup (e) ->
|
|
($ "#btlogin").click() if e.which is 13
|
|
|
|
startAntOS: (conf) ->
|
|
# clean up things
|
|
Ant.OS.cleanup()
|
|
# get setting from conf
|
|
Ant.OS.systemSetting conf
|
|
#console.log Ant.OS.setting
|
|
# load theme
|
|
Ant.OS.GUI.loadTheme Ant.OS.setting.appearance.theme
|
|
Ant.OS.GUI.wallpaper()
|
|
Ant.OS.announcer.observable.one "syspanelloaded", () ->
|
|
# TODO load packages list then build system menu
|
|
Ant.OS.announcer.observable.on "systemlocalechange", (name) ->
|
|
($ "#syspanel")[0].update()
|
|
|
|
Ant.OS.API.packages.cache().then (ret) ->
|
|
if ret.result
|
|
Ant.OS.API.packages.fetch().then (r) ->
|
|
if r.result
|
|
for k, v of r.result
|
|
v.text = v.name
|
|
v.filename = k
|
|
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
|
|
Ant.OS.setting.system.packages = if r.result then r.result else
|
|
# Ant.OS.GUI.refreshSystemMenu()
|
|
# Ant.OS.GUI.buildSystemMenu()
|
|
# push startup services
|
|
# TODO: get services list from user setting
|
|
Ant.OS.GUI.pushServices (v for v in Ant.OS.setting.system.startup.services)
|
|
(Ant.OS.GUI.launch a) for a in Ant.OS.setting.system.startup.apps
|
|
#Ant.OS.GUI.launch "DummyApp"
|
|
# initDM
|
|
Ant.OS.API.setLocale Ant.OS.setting.system.locale
|
|
.then () ->
|
|
Ant.OS.GUI.initDM()
|
|
|
|
|
|
Ant.OS.GUI.schemes = {}
|
|
Ant.OS.GUI.schemes.ws = """
|
|
<afx-sys-panel id = "syspanel"></afx-sys-panel>
|
|
<div id = "workspace">
|
|
<afx-apps-dock id="sysdock"></afx-apps-dock>
|
|
<afx-float-list id = "desktop" dir="vertical" ></afx-float-list>
|
|
</div>
|
|
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu>
|
|
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
|
|
"""
|
|
|
|
Ant.OS.GUI.schemes.login = """
|
|
<div id = "login_form">
|
|
<p>Welcome to AntOS, please identify</p>
|
|
<input id = "txtuser" type = "text" value = "demo" />
|
|
<input id = "txtpass" type = "password" value = "demo" />
|
|
<button id = "btlogin">Login</button>
|
|
<div id = "login_error"></div>
|
|
</div>
|
|
""" |