diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index e266383..bad6458 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -82,6 +82,7 @@ export const deleteInstance = id => defaultDelete("instance", id) export const getPlugins = () => defaultGet("/plugins") export const getPlugin = id => defaultGet(`/plugin/${id}`) +export const deletePlugin = id => defaultDelete("plugin", id) export async function uploadPlugin(data, id) { let resp @@ -124,6 +125,6 @@ export default { BASE_PATH, login, ping, getInstances, getInstance, putInstance, deleteInstance, - getPlugins, getPlugin, uploadPlugin, + getPlugins, getPlugin, uploadPlugin, deletePlugin, getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient, } diff --git a/maubot/management/frontend/src/pages/dashboard/Client.js b/maubot/management/frontend/src/pages/dashboard/Client.js index 24e91f2..5dcb0d2 100644 --- a/maubot/management/frontend/src/pages/dashboard/Client.js +++ b/maubot/management/frontend/src/pages/dashboard/Client.js @@ -220,10 +220,6 @@ class Client extends Component { ) - get hasInstances() { - return this.state.instances.length > 0 - } - renderPrefButtons = () => <>
{!this.isNew && ( @@ -240,6 +236,10 @@ class Client extends Component {
{this.state.error}
+ get hasInstances() { + return this.state.instances.length > 0 + } + renderInstances = () => !this.isNew && (

{this.hasInstances ? "Instances" : "No instances :("}

diff --git a/maubot/management/frontend/src/pages/dashboard/Plugin.js b/maubot/management/frontend/src/pages/dashboard/Plugin.js index eae4022..b7c510e 100644 --- a/maubot/management/frontend/src/pages/dashboard/Plugin.js +++ b/maubot/management/frontend/src/pages/dashboard/Plugin.js @@ -14,8 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import React, { Component } from "react" -import { NavLink } from "react-router-dom" +import { NavLink, Link } from "react-router-dom" import { ReactComponent as ChevronRight } from "../../res/chevron-right.svg" +import { ReactComponent as UploadButton } from "../../res/upload.svg" +import PrefTable, { PrefInput } from "../../components/PreferenceTable" +import Spinner from "../../components/Spinner" +import api from "../../api" const PluginListEntry = ({ plugin }) => ( @@ -28,8 +32,115 @@ const PluginListEntry = ({ plugin }) => ( class Plugin extends Component { static ListEntry = PluginListEntry + constructor(props) { + super(props) + this.state = Object.assign(this.initialState, props.plugin) + } + + get initialState() { + return { + id: "", + version: "", + + instances: [], + + uploading: false, + deleting: false, + error: "", + } + } + + componentWillReceiveProps(nextProps) { + this.setState(Object.assign(this.initialState, nextProps.plugin)) + } + + async readFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsArrayBuffer(file) + reader.onload = evt => resolve(evt.target.result) + reader.onerror = err => reject(err) + }) + } + upload = async event => { + const file = event.target.files[0] + this.setState({ + uploadingAvatar: true, + }) + const data = await this.readFile(file) + const resp = await api.uploadPlugin(data, this.state.id) + if (resp.id) { + if (this.isNew) { + this.props.history.push(`/plugin/${resp.id}`) + } else { + this.setState({ saving: false, error: "" }) + } + this.props.onChange(resp) + } else { + this.setState({ saving: false, error: resp.error }) + } + } + + delete = async () => { + if (!window.confirm(`Are you sure you want to delete ${this.state.id}?`)) { + return + } + this.setState({ deleting: true }) + const resp = await api.deletePlugin(this.state.id) + if (resp.success) { + this.props.history.push("/") + this.props.onDelete() + } else { + this.setState({ deleting: false, error: resp.error }) + } + } + + get isNew() { + return !Boolean(this.props.plugin) + } + + get hasInstances() { + return this.state.instances.length > 0 + } + + renderInstances = () => !this.isNew && ( +
+

{this.hasInstances ? "Instances" : "No instances :("}

+ {this.state.instances.map(instance => ( + + {instance.id} + + ))} +
+ ) + render() { - return
{this.props.id}
+ return
+
+ + evt.target.parentElement.classList.add("drag")} + onDragLeave={evt => evt.target.parentElement.classList.remove("drag")}/> + {this.state.uploading && } +
+ {!this.isNew && <> + + + + +
+ +
+ } +
{this.state.error}
+ {!this.isNew && this.renderInstances()} +
} } diff --git a/maubot/management/frontend/src/style/base/elements.sass b/maubot/management/frontend/src/style/base/elements.sass index 53fd27a..fc0b24f 100644 --- a/maubot/management/frontend/src/style/base/elements.sass +++ b/maubot/management/frontend/src/style/base/elements.sass @@ -46,6 +46,10 @@ &:hover:not(:disabled) background-color: $primary-dark + &:disabled.disabled-bg + background-color: $background-dark !important + color: $text-color + .button +button diff --git a/maubot/management/frontend/src/style/index.sass b/maubot/management/frontend/src/style/index.sass index c876eac..b496d8b 100644 --- a/maubot/management/frontend/src/style/index.sass +++ b/maubot/management/frontend/src/style/index.sass @@ -22,5 +22,8 @@ @import lib/preferencetable @import lib/switch +@import pages/mixins/upload-container +@import pages/mixins/instancelist + @import pages/login @import pages/dashboard diff --git a/maubot/management/frontend/src/style/lib/preferencetable.sass b/maubot/management/frontend/src/style/lib/preferencetable.sass index d6ac7ce..66fcad7 100644 --- a/maubot/management/frontend/src/style/lib/preferencetable.sass +++ b/maubot/management/frontend/src/style/lib/preferencetable.sass @@ -51,6 +51,7 @@ border: none height: 2rem width: 100% + color: $text-color box-sizing: border-box diff --git a/maubot/management/frontend/src/style/pages/client/avatar.sass b/maubot/management/frontend/src/style/pages/client/avatar.sass index 9174ba8..062a605 100644 --- a/maubot/management/frontend/src/style/pages/client/avatar.sass +++ b/maubot/management/frontend/src/style/pages/client/avatar.sass @@ -15,15 +15,11 @@ // along with this program. If not, see . > div.avatar-container - position: relative + +upload-box + width: 8rem height: 8rem border-radius: 50% - overflow: hidden - - display: flex - align-items: center - justify-content: center > img.avatar position: absolute @@ -33,30 +29,16 @@ user-select: none > svg.upload - position: absolute - display: block visibility: hidden width: 6rem height: 6rem - padding: 1rem - user-select: none - > input.file-selector - position: absolute width: 8rem height: 8rem - user-select: none - opacity: 0 - - > div.spinner - +thick-spinner &:not(.uploading) - > input.file-selector - cursor: pointer - &:hover, &.drag > img.avatar opacity: .25 diff --git a/maubot/management/frontend/src/style/pages/client/index.sass b/maubot/management/frontend/src/style/pages/client/index.sass index 2359059..9444513 100644 --- a/maubot/management/frontend/src/style/pages/client/index.sass +++ b/maubot/management/frontend/src/style/pages/client/index.sass @@ -34,4 +34,5 @@ vertical-align: top flex: 1 - @import instances + > div.instances + +instancelist diff --git a/maubot/management/frontend/src/style/pages/dashboard.sass b/maubot/management/frontend/src/style/pages/dashboard.sass index 2d608ce..c5fe3e9 100644 --- a/maubot/management/frontend/src/style/pages/dashboard.sass +++ b/maubot/management/frontend/src/style/pages/dashboard.sass @@ -77,7 +77,7 @@ div.error +notification($error) - margin-top: 1rem + margin: 1rem .5rem &:empty display: none diff --git a/maubot/management/frontend/src/style/pages/client/instances.sass b/maubot/management/frontend/src/style/pages/mixins/instancelist.sass similarity index 98% rename from maubot/management/frontend/src/style/pages/client/instances.sass rename to maubot/management/frontend/src/style/pages/mixins/instancelist.sass index 561a4b6..3ee93df 100644 --- a/maubot/management/frontend/src/style/pages/client/instances.sass +++ b/maubot/management/frontend/src/style/pages/mixins/instancelist.sass @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -> div.instances +=instancelist() margin: 1rem 0 display: flex diff --git a/maubot/management/frontend/src/style/pages/mixins/upload-container.sass b/maubot/management/frontend/src/style/pages/mixins/upload-container.sass new file mode 100644 index 0000000..882c547 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/mixins/upload-container.sass @@ -0,0 +1,43 @@ +// maubot - A plugin-based Matrix bot system. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +=upload-box() + position: relative + overflow: hidden + + display: flex + align-items: center + justify-content: center + + > svg.upload + position: absolute + display: block + + padding: 1rem + user-select: none + + + > input.file-selector + position: absolute + user-select: none + opacity: 0 + + > div.spinner + +thick-spinner + + &:not(.uploading) + > input.file-selector + cursor: pointer diff --git a/maubot/management/frontend/src/style/pages/plugin.sass b/maubot/management/frontend/src/style/pages/plugin.sass index e1376b5..b45d867 100644 --- a/maubot/management/frontend/src/style/pages/plugin.sass +++ b/maubot/management/frontend/src/style/pages/plugin.sass @@ -15,4 +15,41 @@ // along with this program. If not, see . > .plugin - margin: 1rem + margin: 2rem 4rem + + > .upload-box + +upload-box + + width: calc(100% - 1rem) + height: 10rem + margin: .5rem + border-radius: .5rem + box-sizing: border-box + + border: .25rem dotted $primary + + > svg.upload + width: 8rem + height: 8rem + + opacity: .5 + + > input.file-selector + width: 100% + height: 100% + + &:not(.uploading):hover, &:not(.uploading).drag + border: .25rem solid $primary + background-color: $primary-light + + > svg.upload + opacity: 1 + + &.uploading + > svg.upload + visibility: hidden + > input.file-selector + cursor: default + + > div.instances + +instancelist