From 29adf50ae0f308093f32820d9251d7e879faf7fe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 9 Nov 2018 20:03:26 +0200 Subject: [PATCH] Finish initial client main view --- maubot/management/frontend/src/api.js | 24 ++- .../src/components/PreferenceTable.js | 55 ++++++ .../frontend/src/pages/dashboard/Client.js | 161 +++++++++++++----- .../frontend/src/pages/dashboard/index.js | 16 +- .../frontend/src/style/base/elements.sass | 10 +- .../management/frontend/src/style/index.sass | 3 + .../src/style/lib/preferencetable.sass | 57 +++++++ .../frontend/src/style/pages/client.sass | 143 ++++++++++------ .../frontend/src/style/pages/dashboard.sass | 12 +- 9 files changed, 370 insertions(+), 111 deletions(-) create mode 100644 maubot/management/frontend/src/components/PreferenceTable.js create mode 100644 maubot/management/frontend/src/style/lib/preferencetable.sass diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js index d0acbb6..8721685 100644 --- a/maubot/management/frontend/src/api.js +++ b/maubot/management/frontend/src/api.js @@ -72,7 +72,7 @@ export async function uploadPlugin(data, id) { let resp if (id) { resp = await fetch(`${BASE_PATH}/plugin/${id}`, { - headers: getHeaders("applcation/zip"), + headers: getHeaders("application/zip"), body: data, method: "PUT", }) @@ -96,9 +96,27 @@ export async function getClient(id) { return await resp.json() } +export async function uploadAvatar(id, data, mime) { + const resp = await fetch(`${BASE_PATH}/client/${id}/avatar`, { + headers: getHeaders(mime), + body: data, + method: "POST", + }) + return await resp.json() +} + +export async function putClient(client) { + const resp = await fetch(`${BASE_PATH}/client/${client.id}`, { + headers: getHeaders(), + body: JSON.stringify(client), + method: "PUT", + }) + return await resp.json() +} + export default { login, ping, getInstances, getInstance, - getPlugins, getPlugin, uploadPlugin, - getClients, getClient, + getPlugins, getPlugin, uploadPlugin, + getClients, getClient, uploadAvatar, putClient, } diff --git a/maubot/management/frontend/src/components/PreferenceTable.js b/maubot/management/frontend/src/components/PreferenceTable.js new file mode 100644 index 0000000..28ddfab --- /dev/null +++ b/maubot/management/frontend/src/components/PreferenceTable.js @@ -0,0 +1,55 @@ +// 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 . +import React from "react" +import Switch from "./Switch" + +export const PrefTable = ({ children, wrapperClass }) => { + if (wrapperClass) { + return ( +
+
+ {children} +
+
+ ) + } + return ( +
+ {children} +
+ ) +} + +export const PrefRow = ({ name, children }) => ( +
+
{name}
+
{children}
+
+) + +export const PrefInput = ({ rowName, ...args }) => ( + + + +) + +export const PrefSwitch = ({ rowName, ...args }) => ( + + + +) + +export default PrefTable diff --git a/maubot/management/frontend/src/pages/dashboard/Client.js b/maubot/management/frontend/src/pages/dashboard/Client.js index d352b31..2019444 100644 --- a/maubot/management/frontend/src/pages/dashboard/Client.js +++ b/maubot/management/frontend/src/pages/dashboard/Client.js @@ -15,11 +15,16 @@ // along with this program. If not, see . import React, { Component } from "react" import { Link } from "react-router-dom" -import Switch from "../../components/Switch" import { ReactComponent as ChevronRight } from "../../res/chevron-right.svg" import { ReactComponent as UploadButton } from "../../res/upload.svg" +import { PrefTable, PrefSwitch, PrefInput } from "../../components/PreferenceTable" +import Spinner from "../../components/Spinner" +import api from "../../api" function getAvatarURL(client) { + if (!client.avatar_url) { + return "" + } const id = client.avatar_url.substr("mxc://".length) return `${client.homeserver}/_matrix/media/r0/download/${id}` } @@ -45,68 +50,136 @@ class Client extends Component { constructor(props) { super(props) - this.state = props + this.state = Object.assign(this.initialState, props.client) + } + + get initialState() { + return { + id: "", + displayname: "", + homeserver: "", + avatar_url: "", + access_token: "", + sync: true, + autojoin: true, + enabled: true, + started: false, + + uploadingAvatar: false, + saving: false, + startingOrStopping: false, + } } componentWillReceiveProps(nextProps) { - this.setState(nextProps) + this.setState(Object.assign(this.initialState, nextProps.client)) } inputChange = event => { + if (!event.target.name) { + return + } this.setState({ [event.target.name]: event.target.value }) } + 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) + }) + } + + avatarUpload = async event => { + const file = event.target.files[0] + this.setState({ + uploadingAvatar: true, + }) + const data = await this.readFile(file) + const resp = await api.uploadAvatar(this.state.id, data, file.type) + this.setState({ + uploadingAvatar: false, + avatar_url: resp.content_uri, + }) + } + + save = async () => { + this.setState({ saving: true }) + const resp = await api.putClient(this.state) + if (resp.id) { + resp.saving = false + this.setState(resp) + } else { + console.error(resp) + } + } + + startOrStop = async () => { + this.setState({ startingOrStopping: true }) + const resp = await api.putClient({ + id: this.state.id, + started: !this.state.started, + }) + if (resp.id) { + resp.startingOrStopping = false + this.setState(resp) + } else { + console.error(resp) + } + } + render() { return
-
- Avatar - +
+
+ Avatar + + + {this.state.uploadingAvatar && } +
+ {this.props.client && (<> +
+ + {this.state.started ? "Started" : "Stopped"} +
+ + )}
-
-
User ID
-
- + -
-
-
-
Display name
-
- -
-
-
-
Homeserver
-
- -
-
-
-
Access token
-
- -
-
-
-
Sync
-
- + this.setState({ sync })}/> -
-
-
-
Enabled
-
- this.setState({ autojoin })}/> + this.setState({ enabled })}/> -
-
-
+ + +
} } diff --git a/maubot/management/frontend/src/pages/dashboard/index.js b/maubot/management/frontend/src/pages/dashboard/index.js index 34dbb0c..5d9a38d 100644 --- a/maubot/management/frontend/src/pages/dashboard/index.js +++ b/maubot/management/frontend/src/pages/dashboard/index.js @@ -58,11 +58,19 @@ class Dashboard extends Component { } renderView(field, type, id) { - const entry = this.state[field + "s"][id] + const stateField = field + "s" + const entry = this.state[stateField][id] if (!entry) { return "Not found :(" } - return React.createElement(type, entry) + return React.createElement(type, { + [field]: entry, + onChange: newEntry => this.setState({ + [stateField]: Object.assign({}, this.state[stateField], { + [id]: newEntry, + }), + }), + }) } render() { @@ -72,7 +80,9 @@ class Dashboard extends Component { Maubot Manager
- {localStorage.username} +
+ {localStorage.username} +