261 lines
8.1 KiB
Go
261 lines
8.1 KiB
Go
package client
|
|
|
|
import (
|
|
"apigo.cc/apigo/plugin"
|
|
"fmt"
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/ssgo/u"
|
|
webview "github.com/webview/webview_go"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
)
|
|
|
|
var binds = map[string]interface{}{}
|
|
var bindsLock = sync.RWMutex{}
|
|
|
|
func Bind(name string, fn interface{}) {
|
|
bindsLock.Lock()
|
|
binds[name] = fn
|
|
bindsLock.Unlock()
|
|
}
|
|
|
|
func Unbind(name string) {
|
|
bindsLock.Lock()
|
|
delete(binds, name)
|
|
bindsLock.Unlock()
|
|
}
|
|
|
|
type Webview struct {
|
|
w webview.WebView
|
|
isRunning bool
|
|
isAsync bool
|
|
isDebug bool
|
|
webRoot string
|
|
}
|
|
|
|
func addAllDirForWatcher(filename string, watcher *fsnotify.Watcher) {
|
|
watcher.Add(filename)
|
|
for _, f := range u.ReadDirN(filename) {
|
|
if f.IsDir {
|
|
addAllDirForWatcher(f.FullName, watcher)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *Webview) Run() {
|
|
w.isRunning = true
|
|
if w.isDebug && w.webRoot != "" {
|
|
if watcher, err := fsnotify.NewWatcher(); err == nil {
|
|
fmt.Println("watching...", w.webRoot)
|
|
go func() {
|
|
for w.isRunning {
|
|
select {
|
|
case event, ok := <-watcher.Events:
|
|
if !ok {
|
|
break
|
|
}
|
|
if event.Has(fsnotify.Write) {
|
|
fmt.Println("file changed:", event.Name)
|
|
w.Eval("location.reload()")
|
|
}
|
|
case _, ok := <-watcher.Errors:
|
|
if !ok {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
addAllDirForWatcher(w.webRoot, watcher)
|
|
}
|
|
}
|
|
w.w.Run()
|
|
if w.isRunning {
|
|
w.isRunning = false
|
|
w.w.Destroy()
|
|
}
|
|
}
|
|
|
|
func (w *Webview) Close() {
|
|
if w.isRunning {
|
|
w.w.Terminate()
|
|
}
|
|
}
|
|
|
|
func (w *Webview) SetHtml(html string) {
|
|
w.w.SetHtml(html)
|
|
}
|
|
|
|
func (w *Webview) LoadURL(url string) {
|
|
w.w.Navigate(url)
|
|
}
|
|
|
|
func (w *Webview) LoadFile(file string) {
|
|
if !filepath.IsAbs(file) {
|
|
curPath, _ := os.Getwd()
|
|
file = filepath.Join(curPath, file)
|
|
}
|
|
w.webRoot = filepath.Dir(file)
|
|
w.w.Navigate("file://" + file)
|
|
}
|
|
|
|
func (w *Webview) Dispatch(f func()) {
|
|
w.w.Dispatch(f)
|
|
}
|
|
|
|
func (w *Webview) SetTitle(title string) {
|
|
w.w.SetTitle(title)
|
|
}
|
|
|
|
func (w *Webview) SetSize(width int, height int, sizeMode *string) {
|
|
hint := "none"
|
|
if sizeMode != nil {
|
|
hint = *sizeMode
|
|
}
|
|
switch hint {
|
|
case "none":
|
|
w.w.SetSize(width, height, webview.HintNone)
|
|
case "fixed":
|
|
w.w.SetSize(width, height, webview.HintFixed)
|
|
case "min":
|
|
w.w.SetSize(width, height, webview.HintMin)
|
|
case "max":
|
|
w.w.SetSize(width, height, webview.HintMax)
|
|
default:
|
|
w.w.SetSize(width, height, webview.HintNone)
|
|
}
|
|
}
|
|
|
|
func (w *Webview) Eval(code string) {
|
|
w.w.Eval(code)
|
|
}
|
|
|
|
//func (w *Webview) Bind(name string, f interface{}) error {
|
|
// fmt.Println(">>>>>>>>.bind", name, f)
|
|
// return w.w.Bind(name, f)
|
|
//}
|
|
//
|
|
//func (w *Webview) Unbind(name string) error {
|
|
// return w.w.Unbind(name)
|
|
//}
|
|
|
|
func (w *Webview) binds() {
|
|
tmpBinds := map[string]interface{}{}
|
|
bindsLock.RLock()
|
|
for k, v := range binds {
|
|
tmpBinds[k] = v
|
|
}
|
|
bindsLock.RUnlock()
|
|
for k, v := range tmpBinds {
|
|
_ = w.w.Bind(k, v)
|
|
}
|
|
_ = w.w.Bind("__closeWindow", w.Close)
|
|
_ = w.w.Bind("__setTitle", w.w.SetTitle)
|
|
_ = w.w.Bind("__setSize", w.SetSize)
|
|
_ = w.w.Bind("__eval", w.w.Eval)
|
|
w.w.Init(dialogCode)
|
|
w.w.Init(appCode)
|
|
}
|
|
|
|
func newWindow(title string, width int, height int, sizeMode *string, isDebug *bool) *Webview {
|
|
isDebugV := false
|
|
if isDebug != nil {
|
|
isDebugV = *isDebug
|
|
}
|
|
w := &Webview{w: webview.New(isDebugV), isDebug: isDebugV}
|
|
w.SetTitle(title)
|
|
w.SetSize(width, height, sizeMode)
|
|
w.binds()
|
|
return w
|
|
}
|
|
|
|
const appCode = `
|
|
let app = {
|
|
close: __closeWindow,
|
|
setTitle: __setTitle,
|
|
setSize: __setSize,
|
|
eval: __eval,
|
|
}
|
|
`
|
|
|
|
const dialogCode = `
|
|
window._dialogTexts = {'zh':{'Close':'关闭','Cancel':'取消','Confirm':'确定'}}
|
|
function _getDialogDefaultText(text) {return (window._dialogTexts[navigator.language] && window._dialogTexts[navigator.language][text]) || (window._dialogTexts[navigator.language.split('-')[0]] && window._dialogTexts[navigator.language.split('-')[0]][text]) || text}
|
|
window.alert = function (msg, buttonText) {return showDialog(msg, [buttonText || _getDialogDefaultText('Close')])}
|
|
window.confirm = function (msg, confirmButtonText, cancelButtonText) {return showDialog(msg, [cancelButtonText || _getDialogDefaultText('Cancel'), cancelButtonText || _getDialogDefaultText('Confirm')])}
|
|
window.prompt = function (msg, defaultValue, confirmButtonText, cancelButtonText) {return showDialog(msg, [cancelButtonText || _getDialogDefaultText('Cancel'), cancelButtonText || _getDialogDefaultText('Confirm')], [{value:defaultValue||''}])}
|
|
window.showDialog = function (msg, buttons, inputs) {
|
|
if(!document.querySelector('#_dialogStyle')){
|
|
let sDom = document.createElement('style')
|
|
sDom.id = '_dialogStyle'
|
|
sDom.textContent = '._dialogMask {position:fixed;left:0;right:0;top:0;bottom:0;background:rgba(128,128,128,0.7);display:flex;justify-content:center;align-items:center}\n' +
|
|
'._dialogPanel {background:#eee;padding:16px;min-width:300px;max-width:80%;max-height:80%;margin:auto;border-radius:10px;display:flex;flex-flow:column;overflow:auto}\n' +
|
|
'._dialogMessage {font-size:0.875rem;flex:1;overflow:auto;white-space:pre-wrap}\n' +
|
|
'._dialogInputs {display:flex;flex-flow:column;margin-top:8px}\n' +
|
|
'._dialogInputs input {flex:1;border:1px solid #aaa;border-radius:4px;min-height:28px;padding:0 8px}\n' +
|
|
'._dialogLine {text-align:end;padding:4px 0}\n' +
|
|
'._dialogButtons {text-align:end}\n' +
|
|
'._dialogButtons button {background:none;border:none;color:#999;min-height:28px;cursor:pointer;font-size:1rem;margin-left:10px}\n' +
|
|
'._dialogButtons button:hover {color:#999}\n' +
|
|
'._dialogButtons button:last-child {color:#06f}'
|
|
document.head.append(sDom)
|
|
}
|
|
return new Promise(resolve => {
|
|
let a = ['<div class="_dialogPanel">']
|
|
if (msg) a.push('<div class="_dialogMessage">__MSG__</div>'.replace(/__MSG__/, msg))
|
|
if (inputs && inputs.length) {
|
|
let a1 = ['<div class="_dialogInputs">']
|
|
for (let i = 0; i < inputs.length; i++){
|
|
a1.push('<input id="_dialogInput__ID_____INPUT_INDEX__" placeholder="__INPUT_HINT__" value="__INPUT_VALUE__"/>'.replace(/__INPUT_INDEX__/, i + '').replace(/__INPUT_HINT__/, inputs[i].hint||'').replace(/__INPUT_VALUE__/, inputs[i].value||''))
|
|
}
|
|
a1.push('</div>')
|
|
a.push(a1.join(''))
|
|
}
|
|
a.push('<div class="_dialogLine"><hr/></div>')
|
|
let a2 = ['<div class="_dialogButtons">']
|
|
for (let i = 0; i < buttons.length; i++) a2.push('<button class="_dialogButton" onclick="_dialogAction__ID__(__BUTTON_INDEX__)">__BUTTON_LABEL__</button>'.replace(/__BUTTON_INDEX__/, i + '').replace(/__BUTTON_LABEL__/, buttons[i]))
|
|
a2.push('</div>')
|
|
a.push(a2.join(''))
|
|
let dialogId = Math.ceil(Math.random() * 10000000000).toString(36)
|
|
let w = document.createElement('div')
|
|
w.id = '_dialogWindow' + dialogId
|
|
w.className = '_dialogMask'
|
|
window['_dialogAction' + dialogId] = function (index) {
|
|
let isOK = index === buttons.length - 1
|
|
if (inputs && inputs.length) {
|
|
if (isOK) {
|
|
let inputValues = []
|
|
for (let i = 0; i < inputs.length; i++) inputValues.push(document.querySelector('#_dialogInput' + dialogId + '_' + i).value)
|
|
resolve(inputs.length === 1 ? inputValues[0] : inputValues)
|
|
} else {
|
|
resolve(false)
|
|
}
|
|
} else {
|
|
resolve(isOK ? true : index)
|
|
}
|
|
document.body.removeChild(document.querySelector('#_dialogWindow' + dialogId))
|
|
}
|
|
w.innerHTML = a.join('').replace(/__ID__/g, dialogId)
|
|
document.body.append(w)
|
|
})
|
|
}
|
|
`
|
|
|
|
func init() {
|
|
plugin.Register(plugin.Plugin{
|
|
Id: "apigo.cc/apigo/client",
|
|
Name: "web client framework by github.com/webview/webview",
|
|
Objects: map[string]interface{}{
|
|
"newApp": func(title string, width int, height int, sizeMode *string, isDebug *bool) *Webview {
|
|
w := newWindow(title, width, height, sizeMode, isDebug)
|
|
// svc 框架
|
|
//w.w.Init(``)
|
|
return w
|
|
},
|
|
"new": func(title string, width int, height int, sizeMode *string, isDebug *bool) *Webview {
|
|
return newWindow(title, width, height, sizeMode, isDebug)
|
|
},
|
|
},
|
|
})
|
|
}
|