diff --git a/Dockerfile b/Dockerfile index 710b778..ae90a34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,15 @@ +FROM node:10 AS frontend-builder + +COPY ./maubot/management/frontend /frontend +RUN cd /frontend && yarn --prod && yarn build + FROM alpine:3.8 ENV UID=1337 \ GID=1337 COPY . /opt/maubot +COPY --from=frontend-builder /frontend/build /opt/maubot/frontend WORKDIR /opt/maubot RUN apk add --no-cache \ py3-aiohttp \ diff --git a/docker/example-config.yaml b/docker/example-config.yaml index 77f97f0..a273629 100644 --- a/docker/example-config.yaml +++ b/docker/example-config.yaml @@ -24,6 +24,11 @@ server: port: 29316 # The base management API path. base_path: /_matrix/maubot/v1 + # The base path for the UI. + ui_base_path: /_matrix/maubot + # Override path from where to load UI resources. + # Set to false to using pkg_resources to find the path. + override_resource_path: /opt/maubot/frontend # The base appservice API path. Use / for legacy appservice API and /_matrix/app/v1 for v1. appservice_base_path: /_matrix/app/v1 # The shared secret to sign API access tokens. diff --git a/example-config.yaml b/example-config.yaml index b3987f1..1b02d67 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -24,6 +24,11 @@ server: port: 29316 # The base management API path. base_path: /_matrix/maubot/v1 + # The base path for the UI. + ui_base_path: /_matrix/maubot + # Override path from where to load UI resources. + # Set to false to using pkg_resources to find the path. + override_resource_path: false # The base appservice API path. Use / for legacy appservice API and /_matrix/app/v1 for v1. appservice_base_path: /_matrix/app/v1 # The shared secret to sign API access tokens. diff --git a/maubot/__main__.py b/maubot/__main__.py index 3929095..e970c50 100644 --- a/maubot/__main__.py +++ b/maubot/__main__.py @@ -26,7 +26,7 @@ from .server import MaubotServer from .client import Client, init as init_client_class from .loader.zip import init as init_zip_loader from .instance import init as init_plugin_instance_class -from .management.api import init as init_management +from .management.api import init as init_management_api from .__meta__ import __version__ parser = argparse.ArgumentParser(description="A plugin-based Matrix bot system.", @@ -52,8 +52,9 @@ init_zip_loader(config) db_session = init_db(config) clients = init_client_class(db_session, loop) plugins = init_plugin_instance_class(db_session, config, loop) -management_api = init_management(config, loop) -server = MaubotServer(config, management_api, loop) +management_api = init_management_api(config, loop) +server = MaubotServer(config, loop) +server.app.add_subapp(config["server.base_path"], management_api) for plugin in plugins: plugin.load() diff --git a/maubot/client.py b/maubot/client.py index ea7db0d..5b91d8c 100644 --- a/maubot/client.py +++ b/maubot/client.py @@ -177,13 +177,13 @@ class Client: await self.stop() async def update_displayname(self, displayname: str) -> None: - if not displayname or displayname == self.displayname: + if displayname is None or displayname == self.displayname: return self.db_instance.displayname = displayname await self.client.set_displayname(self.displayname) async def update_avatar_url(self, avatar_url: ContentURI) -> None: - if not avatar_url or avatar_url == self.avatar_url: + if avatar_url is None or avatar_url == self.avatar_url: return self.db_instance.avatar_url = avatar_url await self.client.set_avatar_url(self.avatar_url) @@ -198,7 +198,7 @@ class Client: client_session=self.http_client, log=self.log) mxid = await new_client.whoami() if mxid != self.id: - raise ValueError("MXID mismatch") + raise ValueError(f"MXID mismatch: {mxid}") new_client.store = self.db_instance self.stop_sync() self.client = new_client diff --git a/maubot/config.py b/maubot/config.py index cf39d00..ea8dd3c 100644 --- a/maubot/config.py +++ b/maubot/config.py @@ -38,6 +38,9 @@ class Config(BaseFileConfig): copy("server.hostname") copy("server.port") copy("server.listen") + copy("server.base_path") + copy("server.ui_base_path") + copy("server.override_resource_path") copy("server.appservice_base_path") shared_secret = self["server.unshared_secret"] if shared_secret is None or shared_secret == "generate": diff --git a/maubot/instance.py b/maubot/instance.py index 00f8be7..c797466 100644 --- a/maubot/instance.py +++ b/maubot/instance.py @@ -186,10 +186,27 @@ class PluginInstance: self.db_instance.primary_user = client.id self.client.references.remove(self) self.client = client + self.client.references.add(self) await self.start() self.log.debug(f"Primary user switched to {self.client.id}") return True + async def update_type(self, type: str) -> bool: + if not type or type == self.type: + return True + try: + loader = PluginLoader.find(type) + except KeyError: + return False + await self.stop() + self.db_instance.type = loader.id + self.loader.references.remove(self) + self.loader = loader + self.loader.references.add(self) + await self.start() + self.log.debug(f"Type switched to {self.loader.id}") + return True + async def update_started(self, started: bool) -> None: if started is not None and started != self.started: await (self.start() if started else self.stop()) diff --git a/maubot/management/api/auth.py b/maubot/management/api/auth.py index 34303d1..e754809 100644 --- a/maubot/management/api/auth.py +++ b/maubot/management/api/auth.py @@ -13,6 +13,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from typing import Optional from time import time import json @@ -39,13 +40,31 @@ def create_token(user: UserID) -> str: }) -@routes.post("/auth/ping") -async def ping(request: web.Request) -> web.Response: +def get_token(request: web.Request) -> str: token = request.headers.get("Authorization", "") if not token or not token.startswith("Bearer "): + token = request.query.get("access_token", None) + else: + token = token[len("Bearer "):] + return token + + +def check_token(request: web.Request) -> Optional[web.Response]: + token = get_token(request) + if not token: + return resp.no_token + elif not is_valid_token(token): + return resp.invalid_token + return None + + +@routes.post("/auth/ping") +async def ping(request: web.Request) -> web.Response: + token = get_token(request) + if not token: return resp.no_token - data = verify_token(get_config()["server.unshared_secret"], token[len("Bearer "):]) + data = verify_token(get_config()["server.unshared_secret"], token) if not data: return resp.invalid_token user = data.get("user_id", None) diff --git a/maubot/management/api/base.py b/maubot/management/api/base.py index d9c2077..4cf9636 100644 --- a/maubot/management/api/base.py +++ b/maubot/management/api/base.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from aiohttp import web +from ...__meta__ import __version__ from ...config import Config routes: web.RouteTableDef = web.RouteTableDef() @@ -28,3 +29,10 @@ def set_config(config: Config) -> None: def get_config() -> Config: return _config + + +@routes.get("/version") +async def version(_: web.Request) -> web.Response: + return web.json_response({ + "version": __version__ + }) diff --git a/maubot/management/api/client.py b/maubot/management/api/client.py index 872c965..2975581 100644 --- a/maubot/management/api/client.py +++ b/maubot/management/api/client.py @@ -20,7 +20,7 @@ from http import HTTPStatus from aiohttp import web from mautrix.types import UserID, SyncToken, FilterID -from mautrix.errors import MatrixRequestError, MatrixInvalidToken +from mautrix.errors import MatrixRequestError, MatrixConnectionError, MatrixInvalidToken from mautrix.client import Client as MatrixClient from ...db import DBClient @@ -54,12 +54,14 @@ async def _create_client(user_id: Optional[UserID], data: dict) -> web.Response: return resp.bad_client_access_token except MatrixRequestError: return resp.bad_client_access_details + except MatrixConnectionError: + return resp.bad_client_connection_details if user_id is None: existing_client = Client.get(mxid, None) if existing_client is not None: return resp.user_exists elif mxid != user_id: - return resp.mxid_mismatch + return resp.mxid_mismatch(mxid) db_instance = DBClient(id=mxid, homeserver=homeserver, access_token=access_token, enabled=data.get("enabled", True), next_batch=SyncToken(""), filter_id=FilterID(""), sync=data.get("sync", True), @@ -81,8 +83,10 @@ async def _update_client(client: Client, data: dict) -> web.Response: return resp.bad_client_access_token except MatrixRequestError: return resp.bad_client_access_details - except ValueError: - return resp.mxid_mismatch + except MatrixConnectionError: + return resp.bad_client_connection_details + except ValueError as e: + return resp.mxid_mismatch(str(e)[len("MXID mismatch: "):]) await client.update_avatar_url(data.get("avatar_url", None)) await client.update_displayname(data.get("displayname", None)) await client.update_started(data.get("started", None)) @@ -127,3 +131,27 @@ async def delete_client(request: web.Request) -> web.Response: await client.stop() client.delete() return resp.deleted + + +@routes.post("/client/{id}/avatar") +async def upload_avatar(request: web.Request) -> web.Response: + user_id = request.match_info.get("id", None) + client = Client.get(user_id, None) + if not client: + return resp.client_not_found + content = await request.read() + return web.json_response({ + "content_uri": await client.client.upload_media( + content, request.headers.get("Content-Type", None)), + }) + + +@routes.get("/client/{id}/avatar") +async def download_avatar(request: web.Request) -> web.Response: + user_id = request.match_info.get("id", None) + client = Client.get(user_id, None) + if not client: + return resp.client_not_found + if not client.avatar_url or client.avatar_url == "disable": + return web.Response() + return web.Response(body=await client.client.download_media(client.avatar_url)) diff --git a/maubot/management/api/instance.py b/maubot/management/api/instance.py index 57cf2f3..ad7f429 100644 --- a/maubot/management/api/instance.py +++ b/maubot/management/api/instance.py @@ -70,6 +70,7 @@ async def _update_instance(instance: PluginInstance, data: dict) -> web.Response instance.update_enabled(data.get("enabled", None)) instance.update_config(data.get("config", None)) await instance.update_started(data.get("started", None)) + await instance.update_type(data.get("type", None)) instance.db.commit() return resp.updated(instance.to_dict()) diff --git a/maubot/management/api/middleware.py b/maubot/management/api/middleware.py index 2fefbe8..79bc26b 100644 --- a/maubot/management/api/middleware.py +++ b/maubot/management/api/middleware.py @@ -19,7 +19,7 @@ import logging from aiohttp import web from .responses import resp -from .auth import is_valid_token +from .auth import check_token Handler = Callable[[web.Request], Awaitable[web.Response]] @@ -28,12 +28,7 @@ Handler = Callable[[web.Request], Awaitable[web.Response]] async def auth(request: web.Request, handler: Handler) -> web.Response: if "/auth/" in request.path: return await handler(request) - token = request.headers.get("Authorization", "") - if not token or not token.startswith("Bearer "): - return resp.no_token - if not is_valid_token(token[len("Bearer "):]): - return resp.invalid_token - return await handler(request) + return check_token(request) or await handler(request) log = logging.getLogger("maubot.server") diff --git a/maubot/management/api/plugin.py b/maubot/management/api/plugin.py index 4fbb209..3113124 100644 --- a/maubot/management/api/plugin.py +++ b/maubot/management/api/plugin.py @@ -69,6 +69,45 @@ async def reload_plugin(request: web.Request) -> web.Response: return resp.ok +@routes.put("/plugin/{id}") +async def put_plugin(request: web.Request) -> web.Response: + plugin_id = request.match_info.get("id", None) + content = await request.read() + file = BytesIO(content) + try: + pid, version = ZippedPluginLoader.verify_meta(file) + except MaubotZipImportError as e: + return resp.plugin_import_error(str(e), traceback.format_exc()) + if pid != plugin_id: + return resp.pid_mismatch + plugin = PluginLoader.id_cache.get(plugin_id, None) + if not plugin: + return await upload_new_plugin(content, pid, version) + elif isinstance(plugin, ZippedPluginLoader): + return await upload_replacement_plugin(plugin, content, version) + else: + return resp.unsupported_plugin_loader + + +@routes.post("/plugins/upload") +async def upload_plugin(request: web.Request) -> web.Response: + content = await request.read() + file = BytesIO(content) + try: + pid, version = ZippedPluginLoader.verify_meta(file) + except MaubotZipImportError as e: + return resp.plugin_import_error(str(e), traceback.format_exc()) + plugin = PluginLoader.id_cache.get(pid, None) + if not plugin: + return await upload_new_plugin(content, pid, version) + elif not request.query.get("allow_override"): + return resp.plugin_exists + elif isinstance(plugin, ZippedPluginLoader): + return await upload_replacement_plugin(plugin, content, version) + else: + return resp.unsupported_plugin_loader + + async def upload_new_plugin(content: bytes, pid: str, version: str) -> web.Response: path = os.path.join(get_config()["plugin_directories.upload"], f"{pid}-v{version}.mbp") with open(path, "wb") as p: @@ -86,10 +125,10 @@ async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes, dirname = os.path.dirname(plugin.path) old_filename = os.path.basename(plugin.path) if plugin.version in old_filename: - filename = old_filename.replace(plugin.version, new_version) - if filename == old_filename: - filename = re.sub(f"{re.escape(plugin.version)}(-ts[0-9]+)?", - f"{new_version}-ts{int(time())}", old_filename) + replacement = (new_version if plugin.version != new_version + else f"{new_version}-ts{int(time())}") + filename = re.sub(f"{re.escape(plugin.version)}(-ts[0-9]+)?", + replacement, old_filename) else: filename = old_filename.rstrip(".mbp") filename = f"{filename}-v{new_version}.mbp" @@ -110,20 +149,3 @@ async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes, await plugin.start_instances() ZippedPluginLoader.trash(old_path, reason="update") return resp.updated(plugin.to_dict()) - - -@routes.post("/plugins/upload") -async def upload_plugin(request: web.Request) -> web.Response: - content = await request.read() - file = BytesIO(content) - try: - pid, version = ZippedPluginLoader.verify_meta(file) - except MaubotZipImportError as e: - return resp.plugin_import_error(str(e), traceback.format_exc()) - plugin = PluginLoader.id_cache.get(pid, None) - if not plugin: - return await upload_new_plugin(content, pid, version) - elif isinstance(plugin, ZippedPluginLoader): - return await upload_replacement_plugin(plugin, content, version) - else: - return resp.unsupported_plugin_loader diff --git a/maubot/management/api/responses.py b/maubot/management/api/responses.py index 9c815c2..5204927 100644 --- a/maubot/management/api/responses.py +++ b/maubot/management/api/responses.py @@ -55,12 +55,26 @@ class _Response: }, status=HTTPStatus.BAD_REQUEST) @property - def mxid_mismatch(self) -> web.Response: + def bad_client_connection_details(self) -> web.Response: return web.json_response({ - "error": "The Matrix user ID of the client and the user ID of the access token don't match", + "error": "Could not connect to homeserver", + "errcode": "bad_client_connection_details" + }, status=HTTPStatus.BAD_REQUEST) + + def mxid_mismatch(self, found: str) -> web.Response: + return web.json_response({ + "error": "The Matrix user ID of the client and the user ID of the access token don't " + f"match. Access token is for user {found}", "errcode": "mxid_mismatch", }, status=HTTPStatus.BAD_REQUEST) + @property + def pid_mismatch(self) -> web.Response: + return web.json_response({ + "error": "The ID in the path does not match the ID of the uploaded plugin", + "errcode": "pid_mismatch", + }, status=HTTPStatus.BAD_REQUEST) + @property def bad_auth(self) -> web.Response: return web.json_response({ @@ -138,6 +152,13 @@ class _Response: "errcode": "user_exists", }, status=HTTPStatus.CONFLICT) + @property + def plugin_exists(self) -> web.Response: + return web.json_response({ + "error": "A plugin with the same ID as the uploaded plugin already exists", + "errcode": "plugin_exists" + }, status=HTTPStatus.CONFLICT) + @property def plugin_in_use(self) -> web.Response: return web.json_response({ diff --git a/maubot/management/api/spec.yaml b/maubot/management/api/spec.yaml index 75ec865..af93a65 100644 --- a/maubot/management/api/spec.yaml +++ b/maubot/management/api/spec.yaml @@ -85,6 +85,21 @@ paths: summary: Upload a new plugin description: Upload a new plugin. If the plugin already exists, enabled instances will be restarted. tags: [Plugins] + parameters: + - name: allow_override + in: query + description: Set to allow overriding existing plugins + required: false + schema: + type: boolean + default: false + requestBody: + content: + application/zip: + schema: + type: string + format: binary + example: The plugin maubot archive (.mbp) responses: 200: description: Plugin uploaded and replaced current version successfully @@ -102,13 +117,8 @@ paths: $ref: '#/components/responses/BadRequest' 401: $ref: '#/components/responses/Unauthorized' - requestBody: - content: - application/zip: - schema: - type: string - format: binary - example: The plugin maubot archive (.mbp) + 409: + description: Plugin already exists and allow_override was not specified. '/plugin/{id}': parameters: - name: id @@ -150,6 +160,39 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + put: + operationId: put_plugin + summary: Upload a new or replacement plugin + description: | + Upload a new or replacement plugin with the specified ID. + A HTTP 400 will be returned if the ID of the uploaded plugin + doesn't match the ID in the path. If the plugin already + exists, enabled instances will be restarted. + tags: [Plugins] + requestBody: + content: + application/zip: + schema: + type: string + format: binary + example: The plugin maubot archive (.mbp) + responses: + 200: + description: Plugin uploaded and replaced current version successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Plugin' + 201: + description: New plugin uploaded successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Plugin' + 400: + $ref: '#/components/responses/BadRequest' + 401: + $ref: '#/components/responses/Unauthorized' /plugin/{id}/reload: parameters: - name: id @@ -356,6 +399,45 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '/client/{id}/avatar': + parameters: + - name: id + in: path + description: The Matrix user ID of the client to get + required: true + schema: + type: string + post: + operationId: upload_avatar + summary: Upload a profile picture for a bot + tags: [Clients] + requestBody: + content: + image/png: + schema: + type: string + format: binary + example: The avatar to upload + image/jpeg: + schema: + type: string + format: binary + example: The avatar to upload + responses: + 200: + description: The avatar was uploaded successfully + content: + application/json: + schema: + type: object + properties: + content_uri: + type: string + description: The MXC URI of the uploaded avatar + 400: + $ref: '#/components/responses/BadRequest' + 401: + $ref: '#/components/responses/Unauthorized' components: responses: diff --git a/maubot/management/frontend/.sass-lint.yml b/maubot/management/frontend/.sass-lint.yml new file mode 100644 index 0000000..61fdcfc --- /dev/null +++ b/maubot/management/frontend/.sass-lint.yml @@ -0,0 +1,27 @@ +options: + merge-default-rules: false + formatter: html + max-warnings: 50 + +files: + include: 'src/style/**/*.sass' + +rules: + extends-before-mixins: 2 + extends-before-declarations: 2 + placeholder-in-extend: 2 + mixins-before-declarations: + - 2 + - exclude: + - breakpoint + - mq + no-warn: 1 + no-debug: 1 + hex-notation: + - 2 + - style: uppercase + indentation: + - 2 + - size: 4 + property-sort-order: + - 0 diff --git a/maubot/management/frontend/package.json b/maubot/management/frontend/package.json index c5cf653..320679b 100644 --- a/maubot/management/frontend/package.json +++ b/maubot/management/frontend/package.json @@ -5,8 +5,11 @@ "dependencies": { "node-sass": "^4.9.4", "react": "^16.6.0", + "react-ace": "^6.2.0", "react-dom": "^16.6.0", - "react-scripts": "2.0.5" + "react-router-dom": "^4.3.1", + "react-scripts": "2.0.5", + "react-select": "^2.1.1" }, "scripts": { "start": "react-scripts start", @@ -22,5 +25,10 @@ "last 2 safari versions", "last 2 ios_saf versions" ], - "proxy": "http://localhost:29316" + "proxy": "http://localhost:29316", + "homepage": ".", + "devDependencies": { + "sass-lint": "^1.12.1", + "sass-lint-auto-fix": "^0.15.0" + } } diff --git a/maubot/management/frontend/public/index.html b/maubot/management/frontend/public/index.html index 932bd2e..886ebf5 100644 --- a/maubot/management/frontend/public/index.html +++ b/maubot/management/frontend/public/index.html @@ -17,18 +17,22 @@ along with this program. If not, see . --> - + + + Maubot Manager - - - -
- + + + +
+ diff --git a/maubot/management/frontend/src/api.js b/maubot/management/frontend/src/api.js new file mode 100644 index 0000000..8d7d9b6 --- /dev/null +++ b/maubot/management/frontend/src/api.js @@ -0,0 +1,130 @@ +// 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 . + +export const BASE_PATH = "/_matrix/maubot/v1" + +function getHeaders(contentType = "application/json") { + return { + "Content-Type": contentType, + "Authorization": `Bearer ${localStorage.accessToken}`, + } +} + +async function defaultDelete(type, id) { + const resp = await fetch(`${BASE_PATH}/${type}/${id}`, { + headers: getHeaders(), + method: "DELETE", + }) + if (resp.status === 204) { + return { + "success": true, + } + } + return await resp.json() +} + +async function defaultPut(type, entry, id = undefined) { + const resp = await fetch(`${BASE_PATH}/${type}/${id || entry.id}`, { + headers: getHeaders(), + body: JSON.stringify(entry), + method: "PUT", + }) + return await resp.json() +} + +async function defaultGet(path) { + const resp = await fetch(`${BASE_PATH}${path}`, { headers: getHeaders() }) + return await resp.json() +} + +export async function login(username, password) { + const resp = await fetch(`${BASE_PATH}/auth/login`, { + method: "POST", + body: JSON.stringify({ + username, + password, + }), + }) + return await resp.json() +} + +export async function ping() { + const response = await fetch(`${BASE_PATH}/auth/ping`, { + method: "POST", + headers: getHeaders(), + }) + const json = await response.json() + if (json.username) { + return json.username + } else if (json.errcode === "auth_token_missing" || json.errcode === "auth_token_invalid") { + return null + } + throw json +} + +export const getInstances = () => defaultGet("/instances") +export const getInstance = id => defaultGet(`/instance/${id}`) +export const putInstance = (instance, id) => defaultPut("instance", instance, id) +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 + if (id) { + resp = await fetch(`${BASE_PATH}/plugin/${id}`, { + headers: getHeaders("application/zip"), + body: data, + method: "PUT", + }) + } else { + resp = await fetch(`${BASE_PATH}/plugins/upload`, { + headers: getHeaders("application/zip"), + body: data, + method: "POST", + }) + } + return await resp.json() +} + +export const getClients = () => defaultGet("/clients") +export const getClient = id => defaultGet(`/clients/${id}`) + +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 function getAvatarURL(id) { + return `${BASE_PATH}/client/${id}/avatar?access_token=${localStorage.accessToken}` +} + +export const putClient = client => defaultPut("client", client) +export const deleteClient = id => defaultDelete("client", id) + +export default { + BASE_PATH, + login, ping, + getInstances, getInstance, putInstance, deleteInstance, + getPlugins, getPlugin, uploadPlugin, deletePlugin, + getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient, +} diff --git a/maubot/management/frontend/src/components/PreferenceTable.js b/maubot/management/frontend/src/components/PreferenceTable.js new file mode 100644 index 0000000..92dc3ce --- /dev/null +++ b/maubot/management/frontend/src/components/PreferenceTable.js @@ -0,0 +1,62 @@ +// 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 Select from "react-select" +import Switch from "./Switch" + +export const PrefTable = ({ children, wrapperClass }) => { + if (wrapperClass) { + return ( +
+
+ {children} +
+
+ ) + } + return ( +
+ {children} +
+ ) +} + +export const PrefRow = ({ name, fullWidth = false, labelFor = undefined, children }) => ( +
+ +
{children}
+
+) + +export const PrefInput = ({ rowName, fullWidth = false, ...args }) => ( + + + +) + +export const PrefSwitch = ({ rowName, fullWidth = false, ...args }) => ( + + + +) + +export const PrefSelect = ({ rowName, fullWidth = false, ...args }) => ( + + + + + {this.state.error &&
{this.state.error}
} + + + } +} + +export default Login diff --git a/maubot/management/frontend/src/pages/Main.js b/maubot/management/frontend/src/pages/Main.js new file mode 100644 index 0000000..63f419c --- /dev/null +++ b/maubot/management/frontend/src/pages/Main.js @@ -0,0 +1,75 @@ +// 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, { Component } from "react" +import { HashRouter as Router, Switch } from "react-router-dom" +import PrivateRoute from "../components/PrivateRoute" +import Spinner from "../components/Spinner" +import api from "../api" +import Dashboard from "./dashboard" +import Login from "./Login" + +class Main extends Component { + constructor(props) { + super(props) + this.state = { + pinged: false, + authed: false, + } + } + + async componentWillMount() { + if (localStorage.accessToken) { + await this.ping() + } + this.setState({ pinged: true }) + } + + async ping() { + try { + const username = await api.ping() + if (username) { + localStorage.username = username + this.setState({ authed: true }) + } else { + delete localStorage.accessToken + } + } catch (err) { + console.error(err) + } + } + + login = async (token) => { + localStorage.accessToken = token + await this.ping() + } + + render() { + if (!this.state.pinged) { + return + } + return +
+ + } + authed={!this.state.authed} to="/"/> + + +
+
+ } +} + +export default Main diff --git a/maubot/management/frontend/src/pages/dashboard/BaseMainView.js b/maubot/management/frontend/src/pages/dashboard/BaseMainView.js new file mode 100644 index 0000000..172a302 --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/BaseMainView.js @@ -0,0 +1,68 @@ +import React, { Component } from "react" +import { Link } from "react-router-dom" + +class BaseMainView extends Component { + constructor(props) { + super(props) + this.state = Object.assign(this.initialState, props.entry) + } + + componentWillReceiveProps(nextProps) { + this.setState(Object.assign(this.initialState, nextProps.entry)) + } + + delete = async () => { + if (!window.confirm(`Are you sure you want to delete ${this.state.id}?`)) { + return + } + this.setState({ deleting: true }) + const resp = await this.deleteFunc(this.state.id) + if (resp.success) { + this.props.history.push("/") + this.props.onDelete() + } else { + this.setState({ deleting: false, error: resp.error }) + } + } + + get initialState() { + throw Error("Not implemented") + } + + get hasInstances() { + return this.state.instances && this.state.instances.length > 0 + } + + get isNew() { + return !this.props.entry + } + + 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) + }) + } + + renderInstances = () => !this.isNew && ( +
+

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

+ {this.state.instances.map(instance => ( + + {instance.id} + + ))} +
+ ) +} + +export default BaseMainView diff --git a/maubot/management/frontend/src/pages/dashboard/Client.js b/maubot/management/frontend/src/pages/dashboard/Client.js new file mode 100644 index 0000000..0424911 --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/Client.js @@ -0,0 +1,214 @@ +// 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 { NavLink, withRouter } from "react-router-dom" +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" +import BaseMainView from "./BaseMainView" + +const ClientListEntry = ({ entry }) => { + const classes = ["client", "entry"] + if (!entry.enabled) { + classes.push("disabled") + } else if (!entry.started) { + classes.push("stopped") + } + return ( + + + {entry.displayname || entry.id} + + + ) +} + +class Client extends BaseMainView { + static ListEntry = ClientListEntry + + constructor(props) { + super(props) + this.deleteFunc = api.deleteClient + } + + get initialState() { + return { + id: "", + displayname: "", + homeserver: "", + avatar_url: "", + access_token: "", + sync: true, + autojoin: true, + enabled: true, + started: false, + + instances: [], + + uploadingAvatar: false, + saving: false, + deleting: false, + startingOrStopping: false, + error: "", + } + } + + get clientInState() { + const client = Object.assign({}, this.state) + delete client.uploadingAvatar + delete client.saving + delete client.deleting + delete client.startingOrStopping + delete client.error + delete client.instances + return client + } + + 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.clientInState) + if (resp.id) { + if (this.isNew) { + this.props.history.push(`/client/${resp.id}`) + } else { + this.setState({ saving: false, error: "" }) + } + this.props.onChange(resp) + } else { + this.setState({ saving: false, error: resp.error }) + } + } + + startOrStop = async () => { + this.setState({ startingOrStopping: true }) + const resp = await api.putClient({ + id: this.props.entry.id, + started: !this.props.entry.started, + }) + if (resp.id) { + this.props.onChange(resp) + this.setState({ startingOrStopping: false, error: "" }) + } else { + this.setState({ startingOrStopping: false, error: resp.error }) + } + } + + get loading() { + return this.state.saving || this.state.startingOrStopping || this.state.deleting + } + + renderSidebar = () => !this.isNew && ( +
+
+ Avatar + + evt.target.parentElement.classList.add("drag")} + onDragLeave={evt => evt.target.parentElement.classList.remove("drag")}/> + {this.state.uploadingAvatar && } +
+
+ + + {this.props.entry.started ? "Started" : + (this.props.entry.enabled ? "Stopped" : "Disabled")} + +
+ {(this.props.entry.started || this.props.entry.enabled) && ( + + )} +
+ ) + + renderPreferences = () => ( + + + + + + + this.setState({ sync })}/> + this.setState({ autojoin })}/> + this.setState({ + enabled, + started: enabled && this.state.started, + })}/> + + ) + + renderPrefButtons = () => <> +
+ {!this.isNew && ( + + )} + +
+
{this.state.error}
+ + + render() { + return
+ {this.renderSidebar()} +
+ {this.renderPreferences()} + {this.renderPrefButtons()} + {this.renderInstances()} +
+
+ } +} + +export default withRouter(Client) diff --git a/maubot/management/frontend/src/pages/dashboard/Instance.js b/maubot/management/frontend/src/pages/dashboard/Instance.js new file mode 100644 index 0000000..2c2f825 --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/Instance.js @@ -0,0 +1,174 @@ +// 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 { NavLink, withRouter } from "react-router-dom" +import AceEditor from "react-ace" +import "brace/mode/yaml" +import "brace/theme/github" +import { ReactComponent as ChevronRight } from "../../res/chevron-right.svg" +import PrefTable, { PrefInput, PrefSelect, PrefSwitch } from "../../components/PreferenceTable" +import api from "../../api" +import Spinner from "../../components/Spinner" +import BaseMainView from "./BaseMainView" + +const InstanceListEntry = ({ entry }) => ( + + {entry.id} + + +) + +class Instance extends BaseMainView { + static ListEntry = InstanceListEntry + + constructor(props) { + super(props) + this.deleteFunc = api.deleteInstance + this.updateClientOptions() + } + + get initialState() { + return { + id: "", + primary_user: "", + enabled: true, + started: true, + type: "", + config: "", + + saving: false, + deleting: false, + error: "", + } + } + + get instanceInState() { + const instance = Object.assign({}, this.state) + delete instance.saving + delete instance.deleting + delete instance.error + return instance + } + + componentWillReceiveProps(nextProps) { + super.componentWillReceiveProps(nextProps) + this.updateClientOptions() + } + + clientSelectEntry = client => client && { + id: client.id, + value: client.id, + label: ( +
+ + {client.displayname || client.id} +
+ ), + } + + updateClientOptions() { + this.clientOptions = Object.values(this.props.ctx.clients).map(this.clientSelectEntry) + } + + save = async () => { + this.setState({ saving: true }) + const resp = await api.putInstance(this.instanceInState, this.props.entry + ? this.props.entry.id : undefined) + if (resp.id) { + if (this.isNew) { + this.props.history.push(`/instance/${resp.id}`) + } else { + if (resp.id !== this.props.entry.id) { + this.props.history.replace(`/instance/${resp.id}`) + } + this.setState({ saving: false, error: "" }) + } + this.props.onChange(resp) + } else { + this.setState({ saving: false, error: resp.error }) + } + } + + get selectedClientEntry() { + return this.state.primary_user + ? this.clientSelectEntry(this.props.ctx.clients[this.state.primary_user]) + : {} + } + + get selectedPluginEntry() { + return { + id: this.state.type, + value: this.state.type, + label: this.state.type, + } + } + + get typeOptions() { + return Object.values(this.props.ctx.plugins).map(plugin => plugin && { + id: plugin.id, + value: plugin.id, + label: plugin.id, + }) + } + + get loading() { + return this.state.deleting || this.state.saving + } + + get isValid() { + return this.state.id && this.state.primary_user && this.state.type + } + + render() { + return
+ + + this.setState({ enabled })}/> + this.setState({ started })}/> + this.setState({ primary_user: id })}/> + this.setState({ type: id })}/> + + this.setState({ config })} + name="config" value={this.state.config} + editorProps={{ + fontSize: "10pt", + $blockScrolling: true, + }}/> +
+ {!this.isNew && ( + + )} + +
+
{this.state.error}
+
+ } +} + +export default withRouter(Instance) diff --git a/maubot/management/frontend/src/pages/dashboard/Plugin.js b/maubot/management/frontend/src/pages/dashboard/Plugin.js new file mode 100644 index 0000000..aac38cf --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/Plugin.js @@ -0,0 +1,97 @@ +// 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 { NavLink } 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" +import BaseMainView from "./BaseMainView" + +const PluginListEntry = ({ entry }) => ( + + {entry.id} + + +) + + +class Plugin extends BaseMainView { + static ListEntry = PluginListEntry + + get initialState() { + return { + id: "", + version: "", + + instances: [], + + uploading: false, + deleting: false, + error: "", + } + } + + 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 }) + } + } + + render() { + return
+ {!this.isNew && + + + } +
+ + 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()} +
+ } +} + +export default Plugin diff --git a/maubot/management/frontend/src/pages/dashboard/index.js b/maubot/management/frontend/src/pages/dashboard/index.js new file mode 100644 index 0000000..567c443 --- /dev/null +++ b/maubot/management/frontend/src/pages/dashboard/index.js @@ -0,0 +1,163 @@ +// 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, { Component } from "react" +import { Route, Switch, Link, withRouter } from "react-router-dom" +import api from "../../api" +import { ReactComponent as Plus } from "../../res/plus.svg" +import Instance from "./Instance" +import Client from "./Client" +import Plugin from "./Plugin" + +class Dashboard extends Component { + constructor(props) { + super(props) + this.state = { + instances: {}, + clients: {}, + plugins: {}, + sidebarOpen: false, + } + window.maubot = this + } + + componentDidUpdate(prevProps) { + if (this.props.location !== prevProps.location) { + this.setState({ sidebarOpen: false }) + } + } + + async componentWillMount() { + const [instanceList, clientList, pluginList] = await Promise.all([ + api.getInstances(), api.getClients(), api.getPlugins()]) + const instances = {} + for (const instance of instanceList) { + instances[instance.id] = instance + } + const clients = {} + for (const client of clientList) { + clients[client.id] = client + } + const plugins = {} + for (const plugin of pluginList) { + plugins[plugin.id] = plugin + } + this.setState({ instances, clients, plugins }) + } + + renderList(field, type) { + return this.state[field] && Object.values(this.state[field]).map(entry => + React.createElement(type, { key: entry.id, entry })) + } + + delete(stateField, id) { + const data = Object.assign({}, this.state[stateField]) + delete data[id] + this.setState({ [stateField]: data }) + } + + add(stateField, entry, oldID = undefined) { + const data = Object.assign({}, this.state[stateField]) + if (oldID && oldID !== entry.id) { + delete data[oldID] + } + data[entry.id] = entry + this.setState({ [stateField]: data }) + } + + renderView(field, type, id) { + const entry = this.state[field][id] + if (!entry) { + return this.renderNotFound(field.slice(0, -1)) + } + return React.createElement(type, { + entry, + onDelete: () => this.delete(field, id), + onChange: newEntry => this.add(field, newEntry, id), + ctx: this.state, + }) + } + + renderNotFound = (thing = "path") => ( +
+ Oops! I'm afraid that {thing} couldn't be found. +
+ ) + + render() { + return
+ + + Maubot Manager + +
+ {localStorage.username} +
+ + + +
+
this.setState({ sidebarOpen: !this.state.sidebarOpen })}> + +
+
+ +
+ + "Hello, World!"}/> + + this.add("instances", newEntry)} + ctx={this.state}/>}/> + this.add("clients", newEntry)}/>}/> + this.add("plugins", newEntry)}/>}/> + + this.renderView("instances", Instance, match.params.id)}/> + + this.renderView("clients", Client, match.params.id)}/> + + this.renderView("plugins", Plugin, match.params.id)}/> + this.renderNotFound()}/> + +
+
+ } +} + +export default withRouter(Dashboard) diff --git a/maubot/management/frontend/src/res/chevron-right.svg b/maubot/management/frontend/src/res/chevron-right.svg new file mode 100644 index 0000000..58ee688 --- /dev/null +++ b/maubot/management/frontend/src/res/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/maubot/management/frontend/src/res/plus.svg b/maubot/management/frontend/src/res/plus.svg new file mode 100644 index 0000000..8030204 --- /dev/null +++ b/maubot/management/frontend/src/res/plus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/maubot/management/frontend/src/res/upload.svg b/maubot/management/frontend/src/res/upload.svg new file mode 100644 index 0000000..f1deea6 --- /dev/null +++ b/maubot/management/frontend/src/res/upload.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/maubot/management/frontend/src/style/base/body.sass b/maubot/management/frontend/src/style/base/body.sass index b45c068..c30dd67 100644 --- a/maubot/management/frontend/src/style/base/body.sass +++ b/maubot/management/frontend/src/style/base/body.sass @@ -18,7 +18,6 @@ body margin: 0 padding: 0 font-size: 16px - background-color: $background-color #root position: fixed @@ -27,27 +26,14 @@ body right: 0 left: 0 -//.lindeb - > header - position: absolute - top: 0 - height: $header-height - left: 0 - right: 0 +.maubot-wrapper + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + background-color: $background-dark - > main - position: absolute - top: $header-height - bottom: 0 - left: 0 - right: 0 - - text-align: center - - > .lindeb-content - text-align: left - display: inline-block - width: 100% - max-width: $max-width - box-sizing: border-box - padding: 0 1rem +.maubot-loading + margin-top: 10rem + width: 10rem diff --git a/maubot/management/frontend/src/style/base/elements.sass b/maubot/management/frontend/src/style/base/elements.sass new file mode 100644 index 0000000..fc0b24f --- /dev/null +++ b/maubot/management/frontend/src/style/base/elements.sass @@ -0,0 +1,120 @@ +// 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 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 . + +=button($width: null, $height: null, $padding: .375rem 1rem) + font-family: $font-stack + padding: $padding + width: $width + height: $height + background-color: $background + border: none + border-radius: .25rem + color: $text-color + box-sizing: border-box + font-size: 1rem + + &.disabled-bg + background-color: $background-dark + + &:not(:disabled) + cursor: pointer + + &:hover + background-color: darken($background, 10%) + +=link-button() + display: inline-block + text-align: center + text-decoration: none + +=main-color-button() + background-color: $primary + color: $inverted-text-color + &:hover:not(:disabled) + background-color: $primary-dark + + &:disabled.disabled-bg + background-color: $background-dark !important + color: $text-color + +.button + +button + + &.main-color + +main-color-button + +=button-group() + width: 100% + display: flex + > button, > .button + flex: 1 + + &:first-of-type + margin-right: .5rem + + &:last-of-type + margin-left: .5rem + + &:first-of-type:last-of-type + margin: 0 + +=vertical-button-group() + display: flex + flex-direction: column + > button, > .button + flex: 1 + border-radius: 0 + + &:first-of-type + border-radius: .25rem .25rem 0 0 + + &:last-of-type + border-radius: 0 0 .25rem .25rem + + &:first-of-type:last-of-type + border-radius: .25rem + +=input($width: null, $height: null, $vertical-padding: .375rem, $horizontal-padding: 1rem, $font-size: 1rem) + font-family: $font-stack + border: 1px solid $border-color + background-color: $background + color: $text-color + width: $width + height: $height + box-sizing: border-box + border-radius: .25rem + padding: $vertical-padding $horizontal-padding + font-size: $font-size + resize: vertical + + &:hover, &:focus + border-color: $primary + + &:focus + border-width: 2px + padding: calc(#{$vertical-padding} - 1px) calc(#{$horizontal-padding} - 1px) + +.input, .textarea + +input + +input + font-family: $font-stack + +=notification($border: $error-dark, $background: transparentize($error-light, 0.5)) + padding: 1rem + border-radius: .25rem + border: 2px solid $border + background-color: $background diff --git a/maubot/management/frontend/src/style/base/vars.sass b/maubot/management/frontend/src/style/base/vars.sass index 9dc77dd..16a95be 100644 --- a/maubot/management/frontend/src/style/base/vars.sass +++ b/maubot/management/frontend/src/style/base/vars.sass @@ -13,16 +13,20 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -$main-color: darken(#50D367, 10%) -$dark-color: darken($main-color, 10%) -$light-color: lighten($main-color, 10%) -$alt-color: darken(#47B9D7, 10%) -$dark-alt-color: darken($alt-color, 10%) -$border-color: #CCC -$error-color: #D35067 + +$primary: #00C853 +$primary-dark: #009624 +$primary-light: #5EFC82 +$secondary: #00B8D4 +$secondary-dark: #0088A3 +$secondary-light: #62EBFF +$error: #B71C1C +$error-dark: #7F0000 +$error-light: #F05545 + +$border-color: #DDD $text-color: #212121 -$background-color: #FAFAFA -$inverted-text-color: $background-color -$font-stack: sans-serif -$max-width: 42.5rem -$header-height: 3.5rem +$background: #FAFAFA +$background-dark: #E7E7E7 +$inverted-text-color: $background +$font-stack: Raleway, sans-serif diff --git a/maubot/management/frontend/src/style/index.sass b/maubot/management/frontend/src/style/index.sass index d3d2eaa..b496d8b 100644 --- a/maubot/management/frontend/src/style/index.sass +++ b/maubot/management/frontend/src/style/index.sass @@ -13,5 +13,17 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +@import lib/spinner + @import base/vars @import base/body +@import base/elements + +@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 new file mode 100644 index 0000000..c72dff1 --- /dev/null +++ b/maubot/management/frontend/src/style/lib/preferencetable.sass @@ -0,0 +1,79 @@ +// 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 . + +.preference-table + display: flex + + width: 100% + + flex-wrap: wrap + + > .entry + display: block + + @media screen and (max-width: 55rem) + width: calc(100% - 1rem) + width: calc(50% - 1rem) + margin: .5rem + + &.full-width + width: 100% + + > label, > .value + display: block + width: 100% + + > label + font-size: 0.875rem + padding-bottom: .25rem + font-weight: lighter + + > .value + > .switch + width: auto + height: 2rem + + > .select + height: 2.5rem + box-sizing: border-box + + > input + border: none + height: 2rem + width: 100% + color: $text-color + + box-sizing: border-box + + padding: .375rem 0 + background-color: $background + + font-size: 1rem + + border-bottom: 1px solid $background + + &.id:disabled + font-family: "Fira Code", monospace + font-weight: bold + + &:not(:disabled) + border-bottom: 1px dotted $primary + + &:hover + border-bottom: 1px solid $primary + + &:focus + border-bottom: 2px solid $primary diff --git a/maubot/management/frontend/src/style/lib/spinner.sass b/maubot/management/frontend/src/style/lib/spinner.sass new file mode 100644 index 0000000..d164586 --- /dev/null +++ b/maubot/management/frontend/src/style/lib/spinner.sass @@ -0,0 +1,65 @@ +$green: #008744 +$blue: #0057e7 +$red: #d62d20 +$yellow: #ffa700 + +.spinner + position: relative + margin: 0 auto + width: 5rem + + &:before + content: "" + display: block + padding-top: 100% + + svg + animation: rotate 2s linear infinite + height: 100% + transform-origin: center center + width: 100% + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + margin: auto + + circle + stroke-dasharray: 1, 200 + stroke-dashoffset: 0 + animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite + stroke-linecap: round + +=white-spinner() + circle + stroke: white !important + +=thick-spinner($thickness: 5) + svg > circle + stroke-width: $thickness + +@keyframes rotate + 100% + transform: rotate(360deg) + +@keyframes dash + 0% + stroke-dasharray: 1, 200 + stroke-dashoffset: 0 + 50% + stroke-dasharray: 89, 200 + stroke-dashoffset: -35px + 100% + stroke-dasharray: 89, 200 + stroke-dashoffset: -124px + +@keyframes color + 100%, 0% + stroke: $red + 40% + stroke: $blue + 66% + stroke: $green + 80%, 90% + stroke: $yellow diff --git a/maubot/management/frontend/src/style/lib/switch.sass b/maubot/management/frontend/src/style/lib/switch.sass new file mode 100644 index 0000000..ed8e908 --- /dev/null +++ b/maubot/management/frontend/src/style/lib/switch.sass @@ -0,0 +1,77 @@ +// 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 . + +.switch + display: flex + + width: 100% + height: 2rem + + cursor: pointer + + border: 1px solid $error-light + border-radius: .25rem + background-color: $background + + box-sizing: border-box + + > .box + display: flex + box-sizing: border-box + width: 50% + height: 100% + + transition: .5s + text-align: center + + border-radius: .15rem 0 0 .15rem + background-color: $error-light + color: $inverted-text-color + + align-items: center + + > .text + box-sizing: border-box + width: 100% + + text-align: center + vertical-align: middle + + color: $inverted-text-color + font-size: 1rem + + user-select: none + + .on + display: none + + .off + display: inline + + + &[data-active=true] + border: 1px solid $primary + > .box + background-color: $primary + transform: translateX(100%) + + border-radius: 0 .15rem .15rem 0 + + .on + display: inline + + .off + display: none diff --git a/maubot/management/frontend/src/style/pages/client/avatar.sass b/maubot/management/frontend/src/style/pages/client/avatar.sass new file mode 100644 index 0000000..25e596a --- /dev/null +++ b/maubot/management/frontend/src/style/pages/client/avatar.sass @@ -0,0 +1,62 @@ +// 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 . + +> div.avatar-container + +upload-box + + width: 8rem + height: 8rem + border-radius: 50% + + @media screen and (max-width: 40rem) + margin: 0 auto 1rem + + > img.avatar + position: absolute + display: block + max-width: 8rem + max-height: 8rem + user-select: none + + > svg.upload + visibility: hidden + + width: 6rem + height: 6rem + + > input.file-selector + width: 8rem + height: 8rem + + &:not(.uploading) + &:hover, &.drag + > img.avatar + opacity: .25 + + > svg.upload + visibility: visible + + &.no-avatar + > img.avatar + visibility: hidden + + > svg.upload + visibility: visible + opacity: .5 + + &.uploading + > 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 new file mode 100644 index 0000000..3d004db --- /dev/null +++ b/maubot/management/frontend/src/style/pages/client/index.sass @@ -0,0 +1,44 @@ +// 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 . + +> div.client + display: flex + + > div.sidebar + vertical-align: top + text-align: center + width: 8rem + margin-right: 1rem + + > div + margin-bottom: 1rem + + @import avatar + @import started + + > div.info + vertical-align: top + flex: 1 + + > div.instances + +instancelist + + @media screen and (max-width: 40rem) + flex-wrap: wrap + + > div.sidebar, > div.info + width: 100% + margin-right: 0 diff --git a/maubot/management/frontend/src/style/pages/client/started.sass b/maubot/management/frontend/src/style/pages/client/started.sass new file mode 100644 index 0000000..0f63095 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/client/started.sass @@ -0,0 +1,41 @@ +// 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 . + +> div.started-container + display: inline-flex + + > span.started + display: inline-block + height: 0 + width: 0 + border-radius: 50% + margin: .5rem + + &.true + background-color: $primary + box-shadow: 0 0 .75rem .75rem $primary + + &.false + background-color: $error-light + box-shadow: 0 0 .75rem .75rem $error-light + + &.disabled + background-color: $border-color + box-shadow: 0 0 .75rem .75rem $border-color + + > span.text + display: inline-block + margin-left: 1rem diff --git a/maubot/management/frontend/src/style/pages/dashboard-grid.css b/maubot/management/frontend/src/style/pages/dashboard-grid.css new file mode 100644 index 0000000..516c7ff --- /dev/null +++ b/maubot/management/frontend/src/style/pages/dashboard-grid.css @@ -0,0 +1,26 @@ +.dashboard { + grid-template: + [row1-start] "title main" 3.5rem [row1-end] + [row2-start] "user main" 2.5rem [row2-end] + [row3-start] "sidebar main" auto [row3-end] + / 15rem auto; +} + + +@media screen and (max-width: 35rem) { + .dashboard { + grid-template: + [row1-start] "topbar" 3.5rem [row1-end] + [row2-start] "main" auto [row2-end] + / auto; + } + + .dashboard.sidebar-open { + grid-template: + [row1-start] "title topbar" 3.5rem [row1-end] + [row2-start] "user main" 2.5rem [row2-end] + [row3-start] "sidebar main" auto [row3-end] + / 15rem 100%; + overflow-x: hidden; + } +} diff --git a/maubot/management/frontend/src/style/pages/dashboard.sass b/maubot/management/frontend/src/style/pages/dashboard.sass new file mode 100644 index 0000000..a5b82bf --- /dev/null +++ b/maubot/management/frontend/src/style/pages/dashboard.sass @@ -0,0 +1,120 @@ +// 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 "dashboard-grid" + +.dashboard + display: grid + height: 100% + max-width: 60rem + margin: auto + box-shadow: 0 .5rem .5rem rgba(0, 0, 0, 0.5) + background-color: $background + + > a.title + grid-area: title + background-color: white + display: flex + align-items: center + justify-content: center + + font-size: 1.35rem + font-weight: bold + + color: $text-color + text-decoration: none + + > img + max-width: 2rem + margin-right: .5rem + + > div.user + grid-area: user + background-color: white + border-bottom: 1px solid $border-color + display: flex + align-items: center + justify-content: center + span + display: flex + align-items: center + justify-content: center + background-color: $primary + color: $inverted-text-color + margin: .375rem .5rem + width: 100% + height: calc(100% - .375rem) + box-sizing: border-box + border-radius: .25rem + + @import sidebar + @import topbar + + @media screen and (max-width: 35rem) + &:not(.sidebar-open) + > nav.sidebar, > a.title, > div.user + display: none !important + + > main.view + grid-area: main + border-left: 1px solid $border-color + + overflow-y: auto + + @import client/index + @import instance + @import plugin + + > .not-found + text-align: center + margin-top: 5rem + font-size: 1.5rem + + > div:not(.not-found) + margin: 2rem 4rem + + @media screen and (max-width: 50rem) + margin: 2rem 1rem + + div.buttons + +button-group + display: flex + margin: 1rem .5rem + width: calc(100% - 1rem) + + div.error + +notification($error) + margin: 1rem .5rem + + &:empty + display: none + + button.delete + background-color: $error-light !important + + &:hover + background-color: $error !important + + button.save, button.delete + +button + +main-color-button + width: 100% + height: 2.5rem + padding: 0 + + > .spinner + +thick-spinner + +white-spinner + width: 2rem diff --git a/maubot/management/frontend/src/style/pages/instance.sass b/maubot/management/frontend/src/style/pages/instance.sass new file mode 100644 index 0000000..18b63a0 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/instance.sass @@ -0,0 +1,35 @@ +// 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 . + +> div.instance + > div.preference-table + .select-client + display: flex + align-items: center + + img.avatar + max-height: 1.375rem + border-radius: 50% + margin-right: .5rem + + > div.ace_editor + z-index: 0 + height: 15rem !important + width: calc(100% - 1rem) !important + font-size: 12px + font-family: "Fira Code", monospace + + margin: .75rem .5rem 1.5rem diff --git a/maubot/management/frontend/src/style/pages/login.sass b/maubot/management/frontend/src/style/pages/login.sass new file mode 100644 index 0000000..c1be6a4 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/login.sass @@ -0,0 +1,62 @@ +// 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 . + +.maubot-wrapper:not(.authenticated) + background-color: $primary + + text-align: center + +.login + width: 25rem + height: 23rem + display: inline-block + box-sizing: border-box + background-color: white + border-radius: .25rem + margin-top: 3rem + + @media screen and (max-width: 27rem) + margin: 3rem 1rem 0 + width: calc(100% - 2rem) + + h1 + color: $primary + margin: 3rem 0 + + input, button + margin: .5rem 2.5rem + height: 3rem + width: calc(100% - 5rem) + box-sizing: border-box + + input + +input + + button + +button($width: calc(100% - 5rem), $height: 3rem, $padding: 0) + +main-color-button + + .spinner + +white-spinner + +thick-spinner + width: 2rem + + &.errored + height: 26.5rem + + .error + +notification($error) + margin: .5rem 2.5rem diff --git a/maubot/management/frontend/src/style/pages/mixins/instancelist.sass b/maubot/management/frontend/src/style/pages/mixins/instancelist.sass new file mode 100644 index 0000000..3ee93df --- /dev/null +++ b/maubot/management/frontend/src/style/pages/mixins/instancelist.sass @@ -0,0 +1,40 @@ +// 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 . + +=instancelist() + margin: 1rem 0 + + display: flex + flex-wrap: wrap + + > h3 + margin: .5rem + width: 100% + + > a.instance + display: block + width: calc(50% - 1rem) + padding: .375rem .5rem + margin: .5rem + background-color: white + border-radius: .25rem + color: $text-color + text-decoration: none + box-sizing: border-box + border: 1px solid $primary + + &:hover + background-color: $primary 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 new file mode 100644 index 0000000..b9fed11 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/plugin.sass @@ -0,0 +1,53 @@ +// 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 . + +> .plugin + > .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 diff --git a/maubot/management/frontend/src/style/pages/sidebar.sass b/maubot/management/frontend/src/style/pages/sidebar.sass new file mode 100644 index 0000000..497ba06 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/sidebar.sass @@ -0,0 +1,69 @@ +// 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 . + +> nav.sidebar + grid-area: sidebar + background-color: white + + padding: .5rem + + overflow-y: auto + + div.list + &:not(:last-of-type) + margin-bottom: 1.5rem + + div.title + h2 + display: inline-block + margin: 0 0 .25rem 0 + font-size: 1.25rem + + a + display: inline-block + float: right + + a.entry + display: block + color: $text-color + text-decoration: none + padding: .25rem + border-radius: .25rem + height: 2rem + box-sizing: border-box + + &:not(:hover) > svg + display: none + + > svg + float: right + + &:hover + background-color: $primary-light + + &.active + background-color: $primary + color: white + + &.client + img.avatar + max-height: 1.5rem + border-radius: 100% + vertical-align: middle + + span.displayname, span.id + margin-left: .25rem + vertical-align: middle diff --git a/maubot/management/frontend/src/style/pages/topbar.sass b/maubot/management/frontend/src/style/pages/topbar.sass new file mode 100644 index 0000000..e7d8312 --- /dev/null +++ b/maubot/management/frontend/src/style/pages/topbar.sass @@ -0,0 +1,74 @@ +// 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 . + +.topbar + background-color: $primary + + display: flex + justify-items: center + align-items: center + padding: 0 .75rem + + @media screen and (min-width: calc(35rem + 1px)) + display: none + +// Hamburger menu based on "Pure CSS Hamburger fold-out menu" codepen by Erik Terwan (MIT license) +// https://codepen.io/erikterwan/pen/EVzeRP + +.hamburger + display: block + user-select: none + cursor: pointer + + > span + display: block + width: 29px + height: 4px + margin-bottom: 5px + position: relative + + background: white + border-radius: 3px + + z-index: 1 + + transform-origin: 4px 0 + + //transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1.0), background 0.5s cubic-bezier(0.77, 0.2, 0.05, 1.0), opacity 0.55s ease + + &:nth-of-type(1) + transform-origin: 0 0 + + &:nth-of-type(3) + transform-origin: 0 100% + + transform: translateY(2px) + + &.active + transform: translateX(1px) translateY(4px) + + &.active > span + opacity: 1 + + &:nth-of-type(1) + transform: rotate(45deg) translate(-2px, -1px) + + &:nth-of-type(2) + opacity: 0 + transform: rotate(0deg) scale(0.2, 0.2) + + &:nth-of-type(3) + transform: rotate(-45deg) translate(0, -1px) diff --git a/maubot/management/frontend/yarn.lock b/maubot/management/frontend/yarn.lock index 64d5190..a0489ab 100644 --- a/maubot/management/frontend/yarn.lock +++ b/maubot/management/frontend/yarn.lock @@ -751,6 +751,13 @@ dependencies: regenerator-runtime "^0.12.0" +"@babel/runtime@^7.1.2": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39" + integrity sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA== + dependencies: + regenerator-runtime "^0.12.0" + "@babel/template@7.0.0-beta.44": version "7.0.0-beta.44" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" @@ -824,6 +831,105 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== +"@emotion/babel-utils@^0.6.4": + version "0.6.10" + resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc" + integrity sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow== + dependencies: + "@emotion/hash" "^0.6.6" + "@emotion/memoize" "^0.6.6" + "@emotion/serialize" "^0.9.1" + convert-source-map "^1.5.1" + find-root "^1.1.0" + source-map "^0.7.2" + +"@emotion/hash@^0.6.2", "@emotion/hash@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.6.tgz#62266c5f0eac6941fece302abad69f2ee7e25e44" + integrity sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ== + +"@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.6": + version "0.6.6" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b" + integrity sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ== + +"@emotion/serialize@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.9.1.tgz#a494982a6920730dba6303eb018220a2b629c145" + integrity sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ== + dependencies: + "@emotion/hash" "^0.6.6" + "@emotion/memoize" "^0.6.6" + "@emotion/unitless" "^0.6.7" + "@emotion/utils" "^0.8.2" + +"@emotion/stylis@^0.7.0": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5" + integrity sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ== + +"@emotion/unitless@^0.6.2", "@emotion/unitless@^0.6.7": + version "0.6.7" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.6.7.tgz#53e9f1892f725b194d5e6a1684a7b394df592397" + integrity sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg== + +"@emotion/utils@^0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc" + integrity sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw== + +"@sentry/core@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.3.0.tgz#f51f86b380637b5f2348cd35fdb96c224023c103" + integrity sha512-VEMRshyF2J2IJkGTF9fnAd2/a/wR+42qrKwCLVeNxHzZm53VAnSfJd8ZA5jnN5RiydQVGDOylqvdfij/LmmsuQ== + dependencies: + "@sentry/hub" "4.3.0" + "@sentry/minimal" "4.3.0" + "@sentry/types" "4.3.0" + "@sentry/utils" "4.3.0" + +"@sentry/hub@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.3.0.tgz#ed7731583d81664057de73e3221a393116362b04" + integrity sha512-9nh4Hcx2tZQVHr5JGy1XZd1RgwC2C+1tNFXu06rcxYB20nhKOKtOAH9STrv64vpnK+70AC4miRdlCgqHOKLdxA== + dependencies: + "@sentry/types" "4.3.0" + "@sentry/utils" "4.3.0" + +"@sentry/minimal@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.3.0.tgz#d83826e713d54c95d4986187d223ef1fabc4e1ed" + integrity sha512-yRN+L3O9mlcjqpWiivqJsm3Cy/YNh0ejQKpxxxPD186tl5nzEkAqRq5jCYelYIAdH1eBvg87H5VNNFr/shHxaQ== + dependencies: + "@sentry/hub" "4.3.0" + "@sentry/types" "4.3.0" + +"@sentry/node@^4.0.0-beta.12": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.3.0.tgz#5a966d6d791d19a064eeede2cb265cf057414a3f" + integrity sha512-2TZHi0XYpSVzKGT/M7mMlXidya4+ZmSZxZCMJ8KlWPljuQxzPoUeGwmYGXOHEsktOGG2eGajDv1hjGtHfamlnA== + dependencies: + "@sentry/core" "4.3.0" + "@sentry/hub" "4.3.0" + "@sentry/types" "4.3.0" + "@sentry/utils" "4.3.0" + cookie "0.3.1" + lsmod "1.0.0" + md5 "2.2.1" + stack-trace "0.0.10" + +"@sentry/types@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.3.0.tgz#a5cec425764a72a0817329ac212c5ce085ecdd05" + integrity sha512-oBAusA+/JFJJlNfHgikyBPTyvmCr/YxZsLWL2FNOBNJkMClW+m7tQ63x/ZAqRSEd8lnJQxJSU7LfAYDH6LcChQ== + +"@sentry/utils@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.3.0.tgz#4d278b55deb6175ada2a78b56716aa2df97cd389" + integrity sha512-cEGygJbyc1SUGxbanvk3+JYadiywJupdEkp6L8rRfxur/nNQZq1Jd7tkdm4I2rz5YFVg+yayadAquvr4DIu9nA== + dependencies: + "@sentry/types" "4.3.0" + "@svgr/core@^2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-2.4.1.tgz#03a407c28c4a1d84305ae95021e8eabfda8fa731" @@ -1044,6 +1150,13 @@ acorn-globals@^4.1.0, acorn-globals@^4.3.0: acorn "^6.0.1" acorn-walk "^6.0.1" +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= + dependencies: + acorn "^3.0.4" + acorn-jsx@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e" @@ -1056,7 +1169,12 @@ acorn-walk@^6.0.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.0.tgz#c957f4a1460da46af4a0388ce28b4c99355b0cbc" integrity sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg== -acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.3, acorn@^5.6.0, acorn@^5.6.2: +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= + +acorn@^5.0.0, acorn@^5.0.3, acorn@^5.5.0, acorn@^5.5.3, acorn@^5.6.0, acorn@^5.6.2: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== @@ -1076,11 +1194,24 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" integrity sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk= +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw= + ajv-keywords@^3.0.0, ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo= +ajv@^4.7.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" @@ -1116,6 +1247,11 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.1.0.tgz#dcfaacc90ef9187de413ec3ef8d5eb981a98808f" integrity sha512-hTv1qPdi+sVEk3jYsdjox5nQI0C9HTbjKShbCdYLKb1LOfNbb7wsF4d7OEKIZoxIHx02tSp3m94jcPW2EfMjmA== +ansi-escapes@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= + ansi-escapes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" @@ -1521,6 +1657,24 @@ babel-plugin-dynamic-import-node@2.2.0: dependencies: object.assign "^4.1.0" +babel-plugin-emotion@^9.2.11: + version "9.2.11" + resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz#319c005a9ee1d15bb447f59fe504c35fd5807728" + integrity sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@emotion/babel-utils" "^0.6.4" + "@emotion/hash" "^0.6.2" + "@emotion/memoize" "^0.6.1" + "@emotion/stylis" "^0.7.0" + babel-plugin-macros "^2.0.0" + babel-plugin-syntax-jsx "^6.18.0" + convert-source-map "^1.5.0" + find-root "^1.1.0" + mkdirp "^0.5.1" + source-map "^0.5.7" + touch "^2.0.1" + babel-plugin-istanbul@^4.1.6: version "4.1.6" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" @@ -1536,7 +1690,7 @@ babel-plugin-jest-hoist@^23.2.0: resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz#e61fae05a1ca8801aadee57a6d66b8cefaf44167" integrity sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc= -babel-plugin-macros@2.4.2: +babel-plugin-macros@2.4.2, babel-plugin-macros@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.4.2.tgz#21b1a2e82e2130403c5ff785cba6548e9b644b28" integrity sha512-NBVpEWN4OQ/bHnu1fyDaAaTPAjnhXCEPqr1RwqxrU7b6tZ2hypp+zX4hlNfmVGfClD5c3Sl6Hfj5TJNF5VG5aA== @@ -1549,6 +1703,11 @@ babel-plugin-named-asset-import@^0.2.2: resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.2.2.tgz#af1290f77e073411ef1a12f17fc458f1111122eb" integrity sha512-NtESBqk8LZuNhBd1BMLxDOh0JPytMs88LwAZFmHg1ZyuGrIAO40dw7p624w+flj0uuhfKTNY8tYKsUEAZGRRFA== +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -1778,6 +1937,11 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" + integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg= + braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -2082,7 +2246,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1: +chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== @@ -2091,7 +2255,7 @@ chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -2107,6 +2271,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + check-types@^7.3.0: version "7.4.0" resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.4.0.tgz#0378ec1b9616ec71f774931a3c6516fad8c152f4" @@ -2172,6 +2341,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.2.5: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + clean-css@4.2.x: version "4.2.1" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" @@ -2179,6 +2353,13 @@ clean-css@4.2.x: dependencies: source-map "~0.6.0" +cli-cursor@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= + dependencies: + restore-cursor "^1.0.1" + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -2312,7 +2493,7 @@ commander@2.17.x, commander@~2.17.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== -commander@^2.11.0: +commander@^2.11.0, commander@^2.15.1, commander@^2.8.1: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== @@ -2362,7 +2543,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: +concat-stream@^1.4.6, concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -2414,7 +2595,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1: +convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: version "1.6.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== @@ -2485,6 +2666,19 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" +create-emotion@^9.2.12: + version "9.2.12" + resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-9.2.12.tgz#0fc8e7f92c4f8bb924b0fef6781f66b1d07cb26f" + integrity sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA== + dependencies: + "@emotion/hash" "^0.6.2" + "@emotion/memoize" "^0.6.1" + "@emotion/stylis" "^0.7.0" + "@emotion/unitless" "^0.6.2" + csstype "^2.5.2" + stylis "^3.5.0" + stylis-rule-sheet "^0.0.10" + create-hash@^1.1.0, create-hash@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -2536,6 +2730,11 @@ cross-spawn@^5.0.1: shebang-command "^1.2.0" which "^1.2.9" +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -2751,6 +2950,11 @@ cssstyle@^1.0.0, cssstyle@^1.1.1: dependencies: cssom "0.3.x" +csstype@^2.5.2: + version "2.5.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff" + integrity sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -2763,6 +2967,13 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + integrity sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8= + dependencies: + es5-ext "^0.10.9" + damerau-levenshtein@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" @@ -2789,7 +3000,7 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2969,6 +3180,11 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" +diff-match-patch@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1" + integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg== + diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -3003,7 +3219,7 @@ dns-txt@^2.0.2: dependencies: buffer-indexof "^1.0.0" -doctrine@1.5.0: +doctrine@1.5.0, doctrine@^1.2.2: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= @@ -3025,6 +3241,13 @@ dom-converter@~0.2: dependencies: utila "~0.4" +dom-helpers@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -3158,6 +3381,14 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +emotion@^9.1.2: + version "9.2.12" + resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.12.tgz#53925aaa005614e65c6e43db8243c843574d1ea9" + integrity sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ== + dependencies: + babel-plugin-emotion "^9.2.11" + create-emotion "^9.2.12" + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -3218,6 +3449,65 @@ es-to-primitive@^1.1.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.46" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" + integrity sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + next-tick "1" + +es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + integrity sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8= + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3240,6 +3530,16 @@ escodegen@^1.11.0, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-config-react-app@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-3.0.4.tgz#83f394d765e7d5af623d773e64609e9c9f2cbeb5" @@ -3392,6 +3692,53 @@ eslint@5.6.0: table "^4.0.3" text-table "^0.2.0" +eslint@^2.7.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11" + integrity sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE= + dependencies: + chalk "^1.1.3" + concat-stream "^1.4.6" + debug "^2.1.1" + doctrine "^1.2.2" + es6-map "^0.1.3" + escope "^3.6.0" + espree "^3.1.6" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^1.1.1" + glob "^7.0.3" + globals "^9.2.0" + ignore "^3.1.2" + imurmurhash "^0.1.4" + inquirer "^0.12.0" + is-my-json-valid "^2.10.0" + is-resolvable "^1.0.0" + js-yaml "^3.5.1" + json-stable-stringify "^1.0.0" + levn "^0.3.0" + lodash "^4.0.0" + mkdirp "^0.5.0" + optionator "^0.8.1" + path-is-absolute "^1.0.0" + path-is-inside "^1.0.1" + pluralize "^1.2.1" + progress "^1.1.8" + require-uncached "^1.0.2" + shelljs "^0.6.0" + strip-json-comments "~1.0.1" + table "^3.7.8" + text-table "~0.2.0" + user-home "^2.0.0" + +espree@^3.1.6: + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" + integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== + dependencies: + acorn "^5.5.0" + acorn-jsx "^3.0.0" + espree@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/espree/-/espree-4.0.0.tgz#253998f20a0f82db5d866385799d912a83a36634" @@ -3439,6 +3786,14 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -3497,6 +3852,11 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -3695,6 +4055,14 @@ figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== +figures@^1.3.5: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -3702,6 +4070,14 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" +file-entry-cache@^1.1.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8" + integrity sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g= + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + file-entry-cache@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" @@ -3797,6 +4173,11 @@ find-cache-dir@^2.0.0: make-dir "^1.0.0" pkg-dir "^3.0.0" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@3.0.0, find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -3912,6 +4293,13 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +front-matter@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb" + integrity sha1-91mDufL0E75ljJPf172M5AePXNs= + dependencies: + js-yaml "^3.4.6" + fs-extra@7.0.0, fs-extra@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.0.tgz#8cc3f47ce07ef7b3593a11b9fb245f7e34c041d6" @@ -3921,6 +4309,15 @@ fs-extra@7.0.0, fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + integrity sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE= + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + fs-extra@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -4001,6 +4398,20 @@ gaze@^1.0.0: dependencies: globule "^1.0.0" +generate-function@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= + dependencies: + is-property "^1.0.0" + get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" @@ -4093,7 +4504,7 @@ globals@^11.1.0, globals@^11.7.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" integrity sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA== -globals@^9.18.0: +globals@^9.18.0, globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== @@ -4130,6 +4541,19 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" +gonzales-pe-sl@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6" + integrity sha1-aoaLw4BkXxQf7rBCxvl/zHG1n+Y= + dependencies: + minimist "1.1.x" + +gonzales-pe-sl@srowhani/gonzales-pe#dev: + version "4.2.3" + resolved "https://codeload.github.com/srowhani/gonzales-pe/tar.gz/3b052416074edc280f7d04bbe40b2e410693c4a3" + dependencies: + minimist "1.1.x" + graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -4316,6 +4740,17 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== +history@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" + integrity sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== + dependencies: + invariant "^2.2.1" + loose-envify "^1.2.0" + resolve-pathname "^2.2.0" + value-equal "^0.4.0" + warning "^3.0.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4330,6 +4765,11 @@ hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== +hoist-non-react-statics@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -4531,6 +4971,11 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" +ignore@^3.1.2: + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" + integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -4635,6 +5080,25 @@ inquirer@6.2.0, inquirer@^6.1.0: strip-ansi "^4.0.0" through "^2.3.6" +inquirer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" + integrity sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34= + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + figures "^1.3.5" + lodash "^4.3.0" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + internal-ip@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-3.0.1.tgz#df5c99876e1d2eb2ea2d74f520e3f669a00ece27" @@ -4643,7 +5107,7 @@ internal-ip@^3.0.1: default-gateway "^2.6.0" ipaddr.js "^1.5.2" -invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -4716,7 +5180,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.5: +is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -4873,6 +5337,22 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== + +is-my-json-valid@^2.10.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175" + integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q== + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -4938,6 +5418,11 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-property@^1.0.0, is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= + is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" @@ -5459,7 +5944,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.12.0, js-yaml@^3.7.0, js-yaml@^3.9.0: +js-yaml@^3.11.0, js-yaml@^3.12.0, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0, js-yaml@^3.9.0: version "3.12.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== @@ -5575,7 +6060,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@^1.0.1: +json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= @@ -5597,6 +6082,13 @@ json5@^0.5.0, json5@^0.5.1: resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -5609,6 +6101,11 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5667,6 +6164,11 @@ kleur@^2.0.1: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +known-css-properties@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4" + integrity sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ== + last-call-webpack-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" @@ -5791,6 +6293,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.capitalize@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" + integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= + lodash.clonedeep@^4.3.2: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -5801,6 +6308,21 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.kebabcase@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" + integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -5841,7 +6363,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@~4.17.10: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -5851,7 +6373,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -5879,6 +6401,11 @@ lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.3: pseudomap "^1.0.2" yallist "^2.1.2" +lsmod@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" + integrity sha1-mgD3bco26yP6BTUK/htYXUKZ5ks= + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -5936,6 +6463,15 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +md5@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + mdn-data@~1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" @@ -5962,6 +6498,11 @@ mem@^4.0.0: mimic-fn "^1.0.0" p-is-promise "^1.1.0" +memoize-one@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.3.tgz#cdfdd942853f1a1b4c71c5336b8c49da0bf0273c" + integrity sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw== + memory-fs@^0.4.0, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -6121,6 +6662,11 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= +minimist@1.1.x: + version "1.1.3" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" + integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= + minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -6236,6 +6782,11 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" + integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -6287,6 +6838,11 @@ neo-async@^2.5.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc" integrity sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw== +next-tick@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -6429,6 +6985,13 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" @@ -6611,6 +7174,11 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= + onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -6895,6 +7463,13 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + integrity sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -6977,6 +7552,11 @@ pkg-up@2.0.0: dependencies: find-up "^2.1.0" +pluralize@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" + integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= + pluralize@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" @@ -7673,6 +8253,11 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= + progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" @@ -7698,7 +8283,7 @@ prompts@^0.1.9: kleur "^2.0.1" sisteransi "^0.1.1" -prop-types@^15.6.2: +prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== @@ -7813,6 +8398,13 @@ raf@3.4.0: dependencies: performance-now "^2.1.0" +raf@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + randomatic@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" @@ -7862,6 +8454,17 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-ace@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-6.2.0.tgz#9ab0fd88cd1ef6712fec4b243b61431964481c11" + integrity sha512-Cr27xFNZV2wlQi+mFjgUWfd3yPZV84Sf7XVrEXkDBZmQ5I/oY3x4KvtBjX6ImN7SCWu3sU6z9F3Zh6jH3/jtzw== + dependencies: + brace "^0.11.1" + diff-match-patch "^1.0.4" + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + prop-types "^15.6.2" + react-app-polyfill@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-0.1.3.tgz#e57bb50f3751dac0e6b3ac27673812c68c679a1d" @@ -7916,6 +8519,43 @@ react-error-overlay@^5.0.5: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.0.5.tgz#716ff1a92fda76bb89a39adf9ce046a5d740e71a" integrity sha512-ab0HWBgxdIsngHtMGU8+8gYFdTBXpUGd4AE4lN2VZvOIlBmWx9dtaWEViihuGSIGosCKPeHCnzFoRWB42UtnLg== +react-input-autosize@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" + integrity sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA== + dependencies: + prop-types "^15.5.8" + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-router-dom@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== + dependencies: + history "^4.7.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" + +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.1" + warning "^4.0.1" + react-scripts@2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-2.0.5.tgz#74b8e9fa6a7c5f0f11221dd18c10df2ae3df3d69" @@ -7970,6 +8610,29 @@ react-scripts@2.0.5: optionalDependencies: fsevents "1.2.4" +react-select@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.1.1.tgz#762d0babd8c7c46a944db51cbb72e4ee117253f9" + integrity sha512-ukie2LJStNfJEJ7wtqA+crAfzYpkpPr86urvmJGisECwsWJob9boCM4zjmKCi5QR7G8uY9+v7ZoliJpeCz/4xw== + dependencies: + classnames "^2.2.5" + emotion "^9.1.2" + memoize-one "^4.0.0" + prop-types "^15.6.0" + raf "^3.4.0" + react-input-autosize "^2.2.1" + react-transition-group "^2.2.1" + +react-transition-group@^2.2.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874" + integrity sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw== + dependencies: + dom-helpers "^3.3.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + react@^16.6.0: version "16.6.0" resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246" @@ -8046,6 +8709,15 @@ readdirp@^2.0.0: micromatch "^3.1.10" readable-stream "^2.0.2" +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" + integrity sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + realpath-native@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.2.tgz#cd51ce089b513b45cf9b1516c82989b51ccc6560" @@ -8265,7 +8937,7 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= -require-uncached@^1.0.3: +require-uncached@^1.0.2, require-uncached@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= @@ -8303,6 +8975,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-pathname@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" + integrity sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8320,6 +8997,14 @@ resolve@1.8.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1: dependencies: path-parse "^1.0.5" +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8363,6 +9048,13 @@ rsvp@^3.3.3: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" integrity sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw== +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" + integrity sha1-yK1KXhEGYeQCp9IbUw4AnyX444k= + dependencies: + once "^1.3.0" + run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -8377,6 +9069,11 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" + integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= + rxjs@^6.1.0: version "6.3.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" @@ -8427,6 +9124,39 @@ sass-graph@^2.2.4: scss-tokenizer "^0.2.3" yargs "^7.0.0" +sass-lint-auto-fix@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/sass-lint-auto-fix/-/sass-lint-auto-fix-0.15.0.tgz#503d1b73aaf5e3e033d4fe3b80f6c7ad80bb7a46" + integrity sha512-Q1WFpm9Ro1S3xPEavqWLIMx8KRTmkoHZh83+aBoqemiMtRB1KwD7VtLz4jreiZP6fZfSXee1Ln3BjHpKaC6hbA== + dependencies: + "@sentry/node" "^4.0.0-beta.12" + chalk "^2.3.2" + commander "^2.15.1" + glob "^7.1.2" + gonzales-pe-sl srowhani/gonzales-pe#dev + js-yaml "^3.11.0" + sass-lint "^1.12.1" + +sass-lint@^1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83" + integrity sha1-Yw9pwhaqIGuCMvsqqQe98zNrbYM= + dependencies: + commander "^2.8.1" + eslint "^2.7.0" + front-matter "2.1.2" + fs-extra "^3.0.1" + glob "^7.0.0" + globule "^1.0.0" + gonzales-pe-sl "^4.2.3" + js-yaml "^3.5.4" + known-css-properties "^0.3.0" + lodash.capitalize "^4.1.0" + lodash.kebabcase "^4.0.0" + merge "^1.2.0" + path-is-absolute "^1.0.0" + util "^0.10.3" + sass-loader@7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d" @@ -8637,6 +9367,11 @@ shell-quote@1.6.1: array-reduce "~0.0.0" jsonify "~0.0.0" +shelljs@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8" + integrity sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg= + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -8664,6 +9399,11 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= + slice-ansi@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" @@ -8774,6 +9514,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.2: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + spdx-correct@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" @@ -8871,6 +9616,11 @@ stable@~0.1.6: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" @@ -9035,6 +9785,11 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +strip-json-comments@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" + integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= + style-loader@0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.0.tgz#8377fefab68416a2e05f1cabd8c3a3acfcce74f1" @@ -9052,6 +9807,16 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" +stylis-rule-sheet@^0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" + integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== + +stylis@^3.5.0: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" + integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -9096,6 +9861,18 @@ symbol-tree@^3.2.2: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= +table@^3.7.8: + version "3.8.3" + resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" + integrity sha1-K7xULw/amGGnVdOUf+/Ys/UThV8= + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + table@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/table/-/table-4.0.3.tgz#00b5e2b602f1794b9acaf9ca908a76386a7813bc" @@ -9169,7 +9946,7 @@ test-exclude@^4.2.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" -text-table@0.2.0, text-table@^0.2.0: +text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= @@ -9268,6 +10045,13 @@ topo@2.x.x: dependencies: hoek "4.x.x" +touch@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/touch/-/touch-2.0.2.tgz#ca0b2a3ae3211246a61b16ba9e6cbf1596287164" + integrity sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A== + dependencies: + nopt "~1.0.10" + tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.4.3, tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -9509,6 +10293,13 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +user-home@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" + integrity sha1-nHC/2Babwdy/SGBODwS4tJzenp8= + dependencies: + os-homedir "^1.0.0" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -9559,6 +10350,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + integrity sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -9599,6 +10395,20 @@ walker@~1.0.5: dependencies: makeerror "1.0.x" +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + dependencies: + loose-envify "^1.0.0" + +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" diff --git a/maubot/server.py b/maubot/server.py index 501677a..06f1c4d 100644 --- a/maubot/server.py +++ b/maubot/server.py @@ -13,33 +13,78 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from aiohttp import web import logging import asyncio +from aiohttp import web +from aiohttp.abc import AbstractAccessLogger +import pkg_resources + from mautrix.api import PathBuilder, Method from .config import Config from .__meta__ import __version__ +class AccessLogger(AbstractAccessLogger): + def log(self, request: web.Request, response: web.Response, time: int): + self.logger.info(f'{request.remote} "{request.method} {request.path} ' + f'{response.status} {response.body_length} ' + f'in {round(time, 4)}s"') + + class MaubotServer: log: logging.Logger = logging.getLogger("maubot.server") - def __init__(self, config: Config, management: web.Application, - loop: asyncio.AbstractEventLoop) -> None: + def __init__(self, config: Config, loop: asyncio.AbstractEventLoop) -> None: self.loop = loop or asyncio.get_event_loop() self.app = web.Application(loop=self.loop) self.config = config - path = PathBuilder(config["server.base_path"]) - self.add_route(Method.GET, path.version, self.version) - self.app.add_subapp(config["server.base_path"], management) - as_path = PathBuilder(config["server.appservice_base_path"]) self.add_route(Method.PUT, as_path.transactions, self.handle_transaction) - self.runner = web.AppRunner(self.app) + self.setup_management_ui() + + self.runner = web.AppRunner(self.app, access_log_class=AccessLogger) + + def setup_management_ui(self) -> None: + ui_base = self.config["server.ui_base_path"] + if ui_base == "/": + ui_base = "" + directory = (self.config["server.override_resource_path"] + or pkg_resources.resource_filename("maubot", "management/frontend/build")) + self.app.router.add_static(f"{ui_base}/static", f"{directory}/static") + self.setup_static_root_files(directory, ui_base) + + with open(f"{directory}/index.html", "r") as file: + index_html = file.read() + + @web.middleware + async def frontend_404_middleware(request, handler): + if hasattr(handler, "__self__") and isinstance(handler.__self__, web.StaticResource): + try: + return await handler(request) + except web.HTTPNotFound: + return web.Response(body=index_html, content_type="text/html") + return await handler(request) + + self.app.middlewares.append(frontend_404_middleware) + self.app.router.add_get(f"{ui_base}/", lambda _: web.Response(body=index_html, + content_type="text/html")) + self.app.router.add_get(ui_base, lambda _: web.HTTPFound(f"{ui_base}/")) + + def setup_static_root_files(self, directory: str, ui_base: str) -> None: + files = { + "asset-manifest.json": "application/json", + "manifest.json": "application/json", + "favicon.png": "image/png", + } + for file, mime in files.items(): + with open(f"{directory}/{file}", "rb") as stream: + data = stream.read() + self.app.router.add_get(f"{ui_base}/{file}", lambda _: web.Response(body=data, + content_type=mime)) def add_route(self, method: Method, path: PathBuilder, handler) -> None: self.app.router.add_route(method.value, str(path), handler)