dwas-manager / dwas_manager / static / app.js
app.js
Raw
/*
Copyright 2020 Cyanic

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/>.
*/

function toggleView(el) {
    [...el.parentNode.children].forEach(child => {
        if (child === el)
            child.classList.remove('hidden')
        else
            child.classList.add('hidden')
    })
}

function contextMenu(x, y, items) {
    if (document.getElementById('ContextMenu'))
        return

    let cm = document.createElement('div')
    cm.id = 'ContextMenu'
    for (item of items) {
        let a = document.createElement('a')
        a.textContent = item.text
        a.onclick = item.onclick
        a.oncontextmenu = e => {
            a.onclick(e)
            e.preventDefault()
        }
        cm.appendChild(a)
    }
    document.body.appendChild(cm)
    const hide = e => {
        window.removeEventListener('click', hide, true)
        window.removeEventListener('contextmenu', hide, true)
        document.body.removeChild(cm)
    }
    window.addEventListener('click', hide, true)
    window.addEventListener('contextmenu', hide, true)

    if (x + cm.clientWidth > window.innerWidth)
        cm.style.right = window.innerWidth - x
    else
        cm.style.left = x
    if (y + cm.clientHeight > window.innerHeight)
        cm.style.bottom = window.innerHeight - y
    else
        cm.style.top = y
}

function runApp(app) {
    return fetch('/runapp?app=' + app)
}

function editApp(appNode) {
    let app = appNode.dataset.app
    fetch('/getapp?app=' + app)
        .then(r => r.text())
        .then(text => {
            const editapp = document.getElementById('editapp')
            toggleView(editapp)
            editapp.innerHTML = ''
            let dark = window.matchMedia(
                '(prefers-color-scheme: dark)').matches
            let cm = CodeMirror(editapp, {
                value: text,
                mode:  "yaml",
                theme: dark ? 'seti' : 'default'
            })
            let save = document.createElement('button')
            save.textContent = 'Save'
            editapp.appendChild(save)

            let saverun = document.createElement('button')
            saverun.textContent = 'Save and run'
            editapp.appendChild(saverun)

            let cancel = document.createElement('button')
            cancel.textContent = 'Cancel'
            cancel.onclick = () => toggleView(apps)
            cancel.style.marginLeft = '4em'
            editapp.appendChild(cancel)

            let error = document.createElement('pre')
            editapp.appendChild(error)

            let savecb = () => (
                fetch('/setapp?app=' + app, {
                    method: 'POST',
                    body: cm.getValue(),
                })
                .then(r => r.json())
                .then(json => {
                    if (json['error'])
                        throw Error(json['error'])

                    const appChildSelector = child => {
                        const appSelector = `.app[data-app="${app}"]`
                        return document.querySelector(appSelector + child)
                    }
                    const nameNode = appChildSelector(' .name')
                    const iconNode = appChildSelector('> img')

                    app = appNode.dataset.app = json.app
                    nameNode.textContent = appNode.dataset.name = json.name
                    iconNode.src = json.favicon || '/static/dwas-app.svg'

                    return json.app
                })
            )
            save.onclick = () => {
                error.textContent = ''
                savecb()
                    .catch(e => {
                        error.textContent = 'Error: ' + e
                    })
            }
            saverun.onclick = () => {
                error.textContent = ''
                savecb()
                    .then(app => runApp(app))
                    .catch(e => {
                        error.textContent = 'Error: ' + e
                    })
            }

        })
        .catch(e => console.error(e))
}

function createApp(data) {
    let app = document.createElement('div')
    app.className = 'app'
    app.dataset.app = data.app
    app.dataset.name = data.name
    app.onclick = () => {
        runApp(app.dataset.app)
    }

    let icon = document.createElement('img')
    icon.src = data.favicon || '/static/dwas-app.svg'
    icon.onload = () => {
        if (icon.naturalHeight < 64)
            icon.style.imageRendering = 'crisp-edges'
    }
    app.appendChild(icon)

    let name = document.createElement('div')
    name.className = 'name'
    name.textContent = data.name
    app.appendChild(name)

    app.addEventListener('contextmenu', e => {
        let items = [{
            text: 'edit',
            onclick: () => editApp(app),
        }, {
            text: 'add to desktop',
            onclick: () => {
                fetch('/addtodesktop?app=' + app.dataset.app)
                    .catch(e => console.error(e))
            },
        }, {
            text: 'delete',
            onclick: () => {
                let name = app.dataset.name
                if (confirm(`Are you sure you want to delete '${name}'?`))
                    fetch('/deleteapp?app=' + app.dataset.app)
                        .then(() => {
                            apps.removeChild(app)
                        })
                        .catch(e => console.error(e))
            },
        }]
        contextMenu(e.clientX, e.clientY, items)
        e.preventDefault()
    }, false)
    return app
}

const apps = document.getElementById('apps')
fetch('/apps')
    .then(r => r.json())
    .then(applist => applist.forEach(data => {
        let app = createApp(data)
        apps.appendChild(app)
    }))
    .catch(e => console.error(e))

apps.addEventListener('contextmenu', e => {
    let items = [{
        text: 'new app',
        onclick: () => {
            fetch('/createapp')
                .then(r => r.json())
                .then(data => {
                    let app = createApp(data)
                    apps.appendChild(app)
                })
                .catch(e => console.error(e))
        },
    }, {
        text: 'reload',
        onclick: () => {location.reload()},
    }]
    contextMenu(e.clientX, e.clientY, items)
    e.preventDefault()
})