antos-frontend/src/core/gui.coffee

451 lines
18 KiB
CoffeeScript
Raw Normal View History

2018-03-15 11:00:24 +01:00
# 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/.
2017-08-07 00:49:24 +02:00
self.OS.GUI =
2018-02-01 19:36:09 +01:00
subwindows: new Object()
2018-01-26 18:57:28 +01:00
dialog: undefined
2018-03-01 20:07:27 +01:00
fullscreen: false
shortcut:
ALT: {}
CTRL: {}
SHIFT: {}
META: {}
2018-03-05 00:20:25 +01:00
SYS_MENU: [
{
text: "__(Applications)",
2018-03-05 00:20:25 +01:00
child: [],
dataid: "sys-apps"
iconclass: "fa fa-adn",
onmenuselect: (d) ->
_GUI.launch d.item.data.app
}
]
2017-08-24 01:53:13 +02:00
htmlToScheme: (html, app, parent) ->
scheme = $.parseHTML html
2018-03-20 15:05:41 +01:00
2018-03-19 00:36:55 +01:00
$(app.scheme).remove() if app.scheme
2017-08-24 01:53:13 +02:00
($ parent).append scheme
riot.mount ($ scheme), { observable: app.observable }
app.scheme = scheme[0]
app.main()
app.show()
loadScheme: (path, app, parent) ->
2018-02-01 19:36:09 +01:00
path.asFileHandler().read (x) ->
2017-08-11 01:58:46 +02:00
return null unless x
2017-08-24 01:53:13 +02:00
_GUI.htmlToScheme x, app, parent
2018-02-01 19:36:09 +01:00
#, (e, s) ->
# _courrier.osfail "Cannot load scheme file: #{path} for #{app.name} (#{app.pid})", e, s
2017-08-27 23:40:02 +02:00
clearTheme: () ->
$ "head link#ostheme"
.attr "href", ""
2017-08-14 00:20:19 +02:00
2018-01-26 18:57:28 +01:00
loadTheme: (name, force) ->
_GUI.clearTheme() if force
2017-08-07 00:49:24 +02:00
path = "resources/themes/#{name}/#{name}.css"
2017-08-11 01:58:46 +02:00
$ "head link#ostheme"
.attr "href", path
2017-08-14 00:20:19 +02:00
pushServices: (srvs) ->
2018-02-16 18:38:14 +01:00
return unless srvs.length > 0
_courrier.observable.one "srvroutineready", () ->
srvs.splice 0, 1
_GUI.pushServices srvs
_GUI.pushService srvs[0]
2018-02-16 18:38:14 +01:00
2018-01-26 18:57:28 +01:00
openDialog: (d, f, title, data) ->
if _GUI.dialog
_GUI.dialog.show()
return
2018-02-01 19:36:09 +01:00
if not _GUI.subwindows[d]
2018-01-26 18:57:28 +01:00
ex = _API.throwe "Dialog"
return _courrier.oserror __("Dialog {0} not found", d), ex, null
2018-02-01 19:36:09 +01:00
_GUI.dialog = new _GUI.subwindows[d]()
2018-01-26 18:57:28 +01:00
_GUI.dialog.parent = _GUI
_GUI.dialog.handler = f
_GUI.dialog.pid = -1
_GUI.dialog.data = data
_GUI.dialog.title = title
_GUI.dialog.init()
2018-01-22 19:59:08 +01:00
pushService: (ph) ->
arr = ph.split "/"
srv = arr[1]
app = arr[0]
2017-08-27 23:40:02 +02:00
return _PM.createProcess srv, _OS.APP[srv] if _OS.APP[srv]
2018-01-22 19:59:08 +01:00
_GUI.loadApp app,
2018-01-24 01:03:14 +01:00
(a) ->
2018-01-22 19:59:08 +01:00
return _PM.createProcess srv, _OS.APP[srv] if _OS.APP[srv]
(e, s) ->
_courrier.trigger "srvroutineready", srv
_courrier.osfail __("Cannot read service script: {0}", srv), e, s
2018-01-22 19:59:08 +01:00
2018-01-26 18:57:28 +01:00
appsByMime: (mime) ->
2018-03-06 00:06:44 +01:00
metas = ( v for k, v of _OS.setting.system.packages when v and v.app )
2018-01-28 02:01:21 +01:00
mimes = ( m.mimes for m in metas when m)
2018-01-26 18:57:28 +01:00
apps = []
# search app by mimes
f = ( arr, idx ) ->
2018-01-28 02:01:21 +01:00
try
arr.filter (m, i) ->
if mime.match (new RegExp m, "g")
2018-03-14 19:17:41 +01:00
return false if (apps.indexOf metas[idx]) >= 0
2018-01-28 02:01:21 +01:00
apps.push metas[idx]
return false
2018-01-26 18:57:28 +01:00
return false
2018-01-28 02:01:21 +01:00
catch e
_courrier.osfail __("Error find app by mimes {0}", mime), e, mime
2018-01-26 18:57:28 +01:00
2018-01-26 19:10:00 +01:00
( f m, i if m ) for m, i in mimes
2018-01-26 18:57:28 +01:00
return apps
2018-01-30 19:06:30 +01:00
appsWithServices: () ->
o = {}
2018-03-06 00:06:44 +01:00
o[k] = v for k, v of _OS.setting.system.packages when v and v.services and v.services.length > 0
2018-01-30 19:06:30 +01:00
o
2018-01-26 18:57:28 +01:00
openWith: (it) ->
return unless it
2018-01-30 14:47:27 +01:00
return _GUI.launch it.app if it.type is "app" and it.app
return _courrier.osinfo __("Application {0} is not executable", it.text) if it.type is "app"
2018-01-26 18:57:28 +01:00
apps = _GUI.appsByMime ( if it.type is "dir" then "dir" else it.mime )
return _courrier.osinfo __("No application available to open {0}", it.filename) if apps.length is 0
2018-01-26 18:57:28 +01:00
return _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 )
_GUI.openDialog "SelectionDialog", ( d ) ->
_GUI.launch d.text, [it.path]
, __("Open with"), list
2018-01-26 18:57:28 +01:00
2017-08-27 23:40:02 +02:00
forceLaunch: (app, args) ->
2018-03-21 11:59:01 +01:00
console.warn "This method is used for developing only, please use the launch method instead"
_GUI.unloadApp app
2017-08-27 23:40:02 +02:00
_GUI.launch app, args
unloadApp: (app) ->
_PM.killAll app, true
($ _OS.APP[app].style).remove() if _OS.APP[app] and _OS.APP[app].style
2018-03-21 11:59:01 +01:00
delete _OS.APP[app]
2018-01-24 01:03:14 +01:00
loadApp: (app, ok, err) ->
2018-03-11 21:31:40 +01:00
path = "os://packages/#{app}"
2018-01-31 19:20:42 +01:00
path = _OS.setting.system.packages[app].path if _OS.setting.system.packages[app].path
js = path + "/main.js"
2018-02-01 19:36:09 +01:00
2018-01-31 19:20:42 +01:00
js.asFileHandler().read (d) ->
2018-02-01 19:36:09 +01:00
# load app meta data
"#{path}/package.json".asFileHandler().read (data) ->
data.path = path
_OS.APP[app].meta = data if _OS.APP[app]
_OS.APP[v].meta = data for v in data.services if data.services
2018-02-19 23:22:30 +01:00
#load css file
css = "#{path}/main.css"
css.asFileHandler().onready (d) ->
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{_API.handler.get}/#{css}" }
.appendTo 'head'
_OS.APP[app].style = el[0] if _OS.APP[app]
ok app
, () ->
#launch
ok app
2018-02-01 19:36:09 +01:00
, "json"
#ok app
2018-01-31 19:20:42 +01:00
, "script"
2017-08-27 23:40:02 +02:00
launch: (app, args) ->
if not _OS.APP[app]
2017-08-11 01:58:46 +02:00
# first load it
2018-01-22 19:59:08 +01:00
_GUI.loadApp app,
(a)->
_PM.createProcess a, _OS.APP[a], args
2017-08-26 16:50:13 +02:00
, (e, s) ->
2017-08-11 01:58:46 +02:00
else
2017-08-14 00:20:19 +02:00
# now launch it
2017-08-27 23:40:02 +02:00
if _OS.APP[app]
_PM.createProcess app, _OS.APP[app], args
2017-08-26 16:50:13 +02:00
dock: (app, meta) ->
2017-08-14 00:20:19 +02:00
# dock an application to a dock
# create a data object
2017-08-15 02:56:04 +02:00
data =
icon: null
iconclass: meta.iconclass || ""
2017-08-26 16:50:13 +02:00
app: app
onbtclick: () -> app.toggle()
2018-01-31 19:20:42 +01:00
# TODO: this path is not good, need to create a blob of it
data.icon = "#{meta.path}/#{meta.icon}" if meta.icon
2017-08-26 16:50:13 +02:00
# TODO: add default app icon class in system setting
# so that it can be themed
2017-08-14 00:20:19 +02:00
data.iconclass = "fa fa-cogs" if (not meta.icon) and (not meta.iconclass)
dock = $ "#sysdock"
2018-03-20 15:05:41 +01:00
app.init()
2017-08-26 16:50:13 +02:00
app.one "rendered", () ->
dock.get(0).newapp data
app.sysdock = dock.get(0)
app.appmenu = ($ "[data-id = 'appmenu']", "#syspanel")[0]
2017-08-14 00:20:19 +02:00
2018-03-01 20:07:27 +01:00
toggleFullscreen: () ->
2018-02-20 23:07:53 +01:00
el = ($ "body")[0]
2018-03-01 20:07:27 +01:00
if _GUI.fullscreen
return document.exitFullscreen() if document.exitFullscreen
return document.mozCancelFullScreen() if document.mozCancelFullScreen
return document.webkitExitFullscreen() if document.webkitExitFullscreen
2018-03-01 20:12:07 +01:00
return document.cancelFullScreen() if document.cancelFullScreen
2018-03-01 20:07:27 +01:00
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
2018-02-20 23:07:53 +01:00
2017-08-14 00:20:19 +02:00
undock: (app) ->
($ "#sysdock").get(0).removeapp app
2017-08-15 02:56:04 +02:00
attachservice: (srv) ->
($ "#syspanel")[0].attachservice srv
srv.init()
detachservice: (srv) ->
($ "#syspanel")[0].detachservice srv
bindContextMenu: (event) ->
handler = (e) ->
if e.contextmenuHandler
e.contextmenuHandler event, ($ "#contextmenu")[0]
else
p = $(e).parent().get(0)
handler p if p isnt ($ "#workspace").get(0)
handler event.target
event.preventDefault()
2018-01-30 19:06:30 +01:00
bindKey: (k, f) ->
arr = k.split "-"
return unless arr.length is 2
fnk = arr[0].toUpperCase()
c = arr[1].toUpperCase()
return unless _GUI.shortcut[fnk]
_GUI.shortcut[fnk][c] = f
2018-03-11 21:31:40 +01:00
wallpaper: (obj) ->
if obj
_OS.setting.appearance.wp = obj
wp = _OS.setting.appearance.wp
$("body").css("background-image", "url(#{_API.handler.get}/#{wp.url})" )
.css("background-size", wp.size)
2018-03-12 00:42:14 +01:00
.css("background-repeat", wp.repeat)
2018-03-11 21:31:40 +01:00
2017-08-11 01:58:46 +02:00
initDM: ->
2017-08-27 23:40:02 +02:00
# check login first
2017-08-11 01:58:46 +02:00
_API.resource "schemes/dm.html", (x) ->
return null unless x
2017-08-15 02:56:04 +02:00
scheme = $.parseHTML x
2017-08-11 01:58:46 +02:00
($ "#wrapper").append scheme
2018-01-26 18:57:28 +01:00
2018-02-28 17:18:00 +01:00
_courrier.observable.one "sysdockloaded", () ->
($ window).bind 'keydown', (event) ->
dock = ($ "#sysdock")[0]
return unless dock
app = dock.get "selectedApp"
#return true unless app
2018-02-28 17:18:00 +01:00
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 _GUI.shortcut[fnk]
return unless _GUI.shortcut[fnk][c]
_GUI.shortcut[fnk][c](event)
event.preventDefault()
2018-01-26 18:57:28 +01:00
# system menu and dock
riot.mount ($ "#syspanel", $ "#wrapper")
riot.mount ($ "#sysdock", $ "#wrapper"), { items: [] }
2017-08-15 02:56:04 +02:00
# context menu
riot.mount ($ "#contextmenu")
($ "#workspace").contextmenu (e) -> _GUI.bindContextMenu e
2018-01-26 18:57:28 +01:00
# desktop default file manager
desktop = $ "#desktop"
2018-01-30 14:47:27 +01:00
fp = _OS.setting.desktop.path.asFileHandler()
2018-01-26 18:57:28 +01:00
desktop[0].fetch = () ->
fn = () ->
fp = _OS.setting.desktop.path.asFileHandler()
2018-01-26 18:57:28 +01:00
fp.read (d) ->
return _courrier.osfail d.error, (_API.throwe "OS.VFS"), d.error if d.error
items = []
$.each d.result, (i, v) ->
return if v.filename[0] is '.' and not _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 "items", items
desktop[0].refresh()
fp.onready () ->
fn()
, ( e ) -> # try to create the path
console.log "#{fp.path} not found"
name = fp.basename
fp.parent().asFileHandler().mk name, (r) ->
ex = _API.throwe "OS.VFS"
if r.error then _courrier.osfail d.error, ex, d.error else fn()
desktop[0].ready = (e) ->
e.observable = _courrier
window.onresize = () ->
_courrier.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 "selected"
_GUI.openWith it
2018-01-27 03:16:06 +01:00
#($ "#workingenv").on "click", (e) ->
# desktop[0].set "selected", -1
2018-01-26 18:57:28 +01:00
desktop.on "click", (e) ->
2018-01-27 03:16:06 +01:00
return unless e.target is desktop[0]
desktop[0].set "selected", -1
2018-01-26 18:57:28 +01:00
($ "#sysdock").get(0).set "selectedApp", null
2018-02-28 16:48:09 +01:00
#console.log "desktop clicked"
2018-01-26 18:57:28 +01:00
desktop[0].contextmenuHandler = (e, m) ->
desktop[0].set "selected", -1 if e.target is desktop[0]
2018-01-27 03:16:06 +01:00
($ "#sysdock").get(0).set "selectedApp", null
2018-01-30 19:06:30 +01:00
menu = [
{ text: __("Open"), dataid: "desktop-open" },
{ text: __("Refresh"), dataid: "desktop-refresh" }
2018-01-30 19:06:30 +01:00
]
2018-01-31 15:41:32 +01:00
menu = menu.concat ( v for k, v of _OS.setting.desktop.menu)
2018-01-30 19:06:30 +01:00
m.set "items", menu
m.set "onmenuselect", (evt) ->
switch evt.item.data.dataid
when "desktop-open"
it = desktop[0].get "selected"
return _GUI.openWith it if it
it = _OS.setting.desktop.path.asFileHandler()
it.mime = "dir"
_GUI.openWith it
when "desktop-refresh"
desktop[0].fetch()
else
2018-01-31 15:41:32 +01:00
_GUI.launch evt.item.data.app, evt.item.data.args if evt.item.data.app
2018-01-30 19:06:30 +01:00
m.show(e)
2018-01-26 18:57:28 +01:00
desktop[0].fetch()
2018-01-30 14:47:27 +01:00
_courrier.observable.on "VFS", (d) ->
desktop[0].fetch() if d.data.file.hash() is fp.hash() or d.data.file.parent().hash() is fp.hash()
2018-01-31 18:19:02 +01:00
_courrier.ostrigger "desktoploaded"
2018-01-26 18:57:28 +01:00
# mount it
riot.mount desktop
2017-08-27 23:40:02 +02:00
, (e, s) ->
alert __("System fail: Cannot init desktop manager")
2018-01-29 19:16:29 +01:00
console.log s, e
refreshDesktop: () ->
($ "#desktop")[0].fetch()
2018-03-05 00:20:25 +01:00
refreshSystemMenu: () ->
_GUI.SYS_MENU.length = 1
_GUI.SYS_MENU[0].child.length = 0
_GUI.SYS_MENU[0].child.push v for k, v of _OS.setting.system.packages when (v and v.app)
_GUI.SYS_MENU.push v for k, v of _OS.setting.system.menu
_GUI.SYS_MENU.push
text: "__(Toggle Full screen)",
2018-02-20 23:07:53 +01:00
dataid: "os-fullsize",
iconclass: "fa fa-tv"
2018-03-05 00:20:25 +01:00
_GUI.SYS_MENU.push
text: "__(Log out)",
2018-01-29 19:16:29 +01:00
dataid: "sys-logout",
iconclass: "fa fa-user-times"
2018-03-05 00:20:25 +01:00
buildSystemMenu: () ->
menu =
text: ""
iconclass: "fa fa-eercast"
dataid: "sys-menu-root"
child: _GUI.SYS_MENU
2018-01-29 19:16:29 +01:00
menu.onmenuselect = (d) ->
return _OS.exit() if d.item.data.dataid is "sys-logout"
2018-03-01 20:07:27 +01:00
return _GUI.toggleFullscreen() if d.item.data.dataid is "os-fullsize"
2018-01-29 19:16:29 +01:00
_GUI.launch d.item.data.app unless d.item.data.dataid
2018-03-05 00:20:25 +01:00
menu = [menu]
($ "[data-id = 'os_menu']", "#syspanel")[0].set "items", menu
2018-01-29 19:16:29 +01:00
#console.log menu
mkdialog: (conf) ->
return new _GUI.BasicDialog conf.name, conf.layout
2018-01-29 19:16:29 +01:00
2017-08-27 23:40:02 +02:00
login: () ->
_API.resource "schemes/login.html", (x) ->
return null unless x
scheme = $.parseHTML x
($ "#wrapper").append scheme
($ "#btlogin").click () ->
data =
username: ($ "#txtuser").val(),
password: ($ "#txtpass").val()
_API.handler.login data, (d) ->
if d.error then ($ "#login_error").html d.error else _GUI.startAntOS d.result
2018-02-19 17:05:22 +01:00
($ "#txtpass").keyup (e) ->
($ "#btlogin").click() if e.which is 13
2017-08-27 23:40:02 +02:00
, (e, s) ->
alert __("System fail: Cannot init login screen")
2017-08-27 23:40:02 +02:00
startAntOS: (conf) ->
2018-01-26 18:57:28 +01:00
# clean up things
2018-01-04 11:51:12 +01:00
_OS.cleanup()
2018-01-26 18:57:28 +01:00
# get setting from conf
_OS.systemSetting conf
#console.log _OS.setting
2017-08-27 23:40:02 +02:00
# load theme
2018-01-27 03:16:06 +01:00
_GUI.loadTheme _OS.setting.appearance.theme
2018-03-11 21:31:40 +01:00
_GUI.wallpaper()
2017-08-27 23:40:02 +02:00
_courrier.observable.one "syspanelloaded", () ->
2018-01-26 18:57:28 +01:00
# TODO load packages list then build system menu
2018-03-10 15:40:25 +01:00
_courrier.observable.on "systemlocalechange", (name) ->
($ "#syspanel")[0].update()
2018-03-10 15:40:25 +01:00
2018-02-01 19:36:09 +01:00
_API.packages.cache (ret) ->
if ret.result
_API.packages.fetch (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
2018-02-01 19:36:09 +01:00
v.iconclass = "fa fa-adn" unless v.iconclass or v.icon
_OS.setting.system.packages = if r.result then r.result else
2018-03-05 00:20:25 +01:00
_GUI.refreshSystemMenu()
2018-02-01 19:36:09 +01:00
_GUI.buildSystemMenu()
# push startup services
# TODO: get services list from user setting
_GUI.pushServices (v for v in _OS.setting.system.startup.services)
2018-02-28 16:40:28 +01:00
(_GUI.launch a) for a in _OS.setting.system.startup.apps
2018-03-10 15:40:25 +01:00
#_GUI.launch "DummyApp"
# initDM
_API.setLocale _OS.setting.system.locale, () ->
_GUI.initDM()