Room dialog (#905)
* support ToolIcon className and fix label padding * factor some ExportDialog classes out to Modal * initial RoomDialog prototype * change label for another-session button * remove unused css * add color comments * Move the collaboration button to the main menu, add support for mobile * remove button for creating another session * add locks * Fix alignment issue * Reorder button * reuse current scene for collab session * keep collaboration state on restore Co-authored-by: Jed Fox <git@twopointzero.us>
This commit is contained in:
parent
aa9a6b0909
commit
b82b0754ac
@ -1,6 +1,5 @@
|
||||
import { AppState, FlooredNumber } from "./types";
|
||||
import { getDateTime } from "./utils";
|
||||
import { getCollaborationLinkData } from "./data";
|
||||
|
||||
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
|
||||
export const DEFAULT_FONT = "20px Virgil";
|
||||
@ -28,7 +27,7 @@ export function getDefaultAppState(): AppState {
|
||||
cursorY: 0,
|
||||
scrolledOutside: false,
|
||||
name: DEFAULT_PROJECT_NAME,
|
||||
isCollaborating: !!getCollaborationLinkData(window.location.href),
|
||||
isCollaborating: false,
|
||||
isResizing: false,
|
||||
selectionElement: null,
|
||||
zoom: 1,
|
||||
|
@ -190,7 +190,12 @@ export class App extends React.Component<any, AppState> {
|
||||
if (commitToHistory) {
|
||||
history.resumeRecording();
|
||||
}
|
||||
this.setState({ ...res.appState });
|
||||
this.setState(state => ({
|
||||
...res.appState,
|
||||
isCollaborating: state.isCollaborating,
|
||||
remotePointers: state.remotePointers,
|
||||
collaboratorCount: state.collaboratorCount,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@ -226,12 +231,27 @@ export class App extends React.Component<any, AppState> {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
private destroySocketClient = () => {
|
||||
this.setState({
|
||||
isCollaborating: false,
|
||||
});
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
this.socket = null;
|
||||
this.roomID = null;
|
||||
this.roomKey = null;
|
||||
}
|
||||
};
|
||||
|
||||
private initializeSocketClient = () => {
|
||||
if (this.socket) {
|
||||
return;
|
||||
}
|
||||
const roomMatch = getCollaborationLinkData(window.location.href);
|
||||
if (roomMatch) {
|
||||
this.setState({
|
||||
isCollaborating: true,
|
||||
});
|
||||
this.socket = socketIOClient(SOCKET_SERVER);
|
||||
this.roomID = roomMatch[1];
|
||||
this.roomKey = roomMatch[2];
|
||||
@ -611,6 +631,20 @@ export class App extends React.Component<any, AppState> {
|
||||
gesture.pointers.delete(event.pointerId);
|
||||
};
|
||||
|
||||
createRoom = async () => {
|
||||
window.history.pushState(
|
||||
{},
|
||||
"Excalidraw",
|
||||
await generateCollaborationLink(),
|
||||
);
|
||||
this.initializeSocketClient();
|
||||
};
|
||||
|
||||
destroyRoom = () => {
|
||||
window.history.pushState({}, "Excalidraw", window.location.origin);
|
||||
this.destroySocketClient();
|
||||
};
|
||||
|
||||
public render() {
|
||||
const canvasDOMWidth = window.innerWidth;
|
||||
const canvasDOMHeight = window.innerHeight;
|
||||
@ -630,6 +664,8 @@ export class App extends React.Component<any, AppState> {
|
||||
elements={elements}
|
||||
setElements={this.setElements}
|
||||
language={getLanguage()}
|
||||
onRoomCreate={this.createRoom}
|
||||
onRoomDestroy={this.destroyRoom}
|
||||
/>
|
||||
<main>
|
||||
<canvas
|
||||
|
@ -1,28 +1,3 @@
|
||||
.ExportDialog__dialog {
|
||||
/* transition: opacity 0.15s ease-in, transform 0.15s ease-in; */
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
animation: ExportDialog__fade-in 0.1s ease-out 0.05s forwards;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes ExportDialog__fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.ExportDialog__close {
|
||||
position: absolute;
|
||||
right: calc(var(--space-factor) * 5);
|
||||
top: calc(var(--space-factor) * 5);
|
||||
}
|
||||
|
||||
.ExportDialog__preview {
|
||||
--preview-padding: calc(var(--space-factor) * 4);
|
||||
|
||||
|
@ -112,10 +112,10 @@ function ExportModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ExportDialog__dialog" onKeyDown={handleKeyDown}>
|
||||
<div onKeyDown={handleKeyDown}>
|
||||
<Island padding={4}>
|
||||
<button
|
||||
className="ExportDialog__close"
|
||||
className="Modal__close"
|
||||
onClick={onCloseRequest}
|
||||
aria-label={t("buttons.close")}
|
||||
ref={closeButton}
|
||||
|
@ -21,6 +21,7 @@ import { ExportType } from "../scene/types";
|
||||
import { MobileMenu } from "./MobileMenu";
|
||||
import { ZoomActions, SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||
import { Section } from "./Section";
|
||||
import { RoomDialog } from "./RoomDialog";
|
||||
|
||||
interface LayerUIProps {
|
||||
actionManager: ActionManager;
|
||||
@ -30,6 +31,8 @@ interface LayerUIProps {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
language: string;
|
||||
setElements: (elements: readonly ExcalidrawElement[]) => void;
|
||||
onRoomCreate: () => void;
|
||||
onRoomDestroy: () => void;
|
||||
}
|
||||
|
||||
export const LayerUI = React.memo(
|
||||
@ -41,6 +44,8 @@ export const LayerUI = React.memo(
|
||||
elements,
|
||||
language,
|
||||
setElements,
|
||||
onRoomCreate,
|
||||
onRoomDestroy,
|
||||
}: LayerUIProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
@ -92,21 +97,28 @@ export const LayerUI = React.memo(
|
||||
actionManager={actionManager}
|
||||
exportButton={renderExportDialog()}
|
||||
setAppState={setAppState}
|
||||
onRoomCreate={onRoomCreate}
|
||||
onRoomDestroy={onRoomDestroy}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FixedSideContainer side="top">
|
||||
<HintViewer appState={appState} elements={elements} />
|
||||
<div className="App-menu App-menu_top">
|
||||
<Stack.Col gap={4} align="end">
|
||||
<Stack.Col gap={4}>
|
||||
<Section className="App-right-menu" heading="canvasActions">
|
||||
<Island padding={4}>
|
||||
<Stack.Col gap={4}>
|
||||
<Stack.Row justifyContent={"space-between"}>
|
||||
<Stack.Row gap={2.25} justifyContent={"space-between"}>
|
||||
{actionManager.renderAction("loadScene")}
|
||||
{actionManager.renderAction("saveScene")}
|
||||
{renderExportDialog()}
|
||||
{actionManager.renderAction("clearCanvas")}
|
||||
<RoomDialog
|
||||
isCollaborating={appState.isCollaborating}
|
||||
onRoomCreate={onRoomCreate}
|
||||
onRoomDestroy={onRoomDestroy}
|
||||
/>
|
||||
</Stack.Row>
|
||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||
</Stack.Col>
|
||||
|
@ -12,6 +12,7 @@ import { HintViewer } from "./HintViewer";
|
||||
import { calculateScrollCenter, getTargetElement } from "../scene";
|
||||
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
|
||||
import { Section } from "./Section";
|
||||
import { RoomDialog } from "./RoomDialog";
|
||||
|
||||
type MobileMenuProps = {
|
||||
appState: AppState;
|
||||
@ -20,6 +21,8 @@ type MobileMenuProps = {
|
||||
setAppState: any;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
setElements: any;
|
||||
onRoomCreate: () => void;
|
||||
onRoomDestroy: () => void;
|
||||
};
|
||||
|
||||
export function MobileMenu({
|
||||
@ -29,6 +32,8 @@ export function MobileMenu({
|
||||
actionManager,
|
||||
exportButton,
|
||||
setAppState,
|
||||
onRoomCreate,
|
||||
onRoomDestroy,
|
||||
}: MobileMenuProps) {
|
||||
return (
|
||||
<>
|
||||
@ -40,6 +45,11 @@ export function MobileMenu({
|
||||
{actionManager.renderAction("saveScene")}
|
||||
{exportButton}
|
||||
{actionManager.renderAction("clearCanvas")}
|
||||
<RoomDialog
|
||||
isCollaborating={appState.isCollaborating}
|
||||
onRoomCreate={onRoomCreate}
|
||||
onRoomDestroy={onRoomDestroy}
|
||||
/>
|
||||
{actionManager.renderAction("changeViewBackgroundColor")}
|
||||
<fieldset>
|
||||
<legend>{t("labels.language")}</legend>
|
||||
|
@ -27,4 +27,26 @@
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
animation: Modal__content_fade-in 0.1s ease-out 0.05s forwards;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes Modal__content_fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.Modal__close {
|
||||
position: absolute;
|
||||
right: calc(var(--space-factor) * 5);
|
||||
top: calc(var(--space-factor) * 5);
|
||||
}
|
||||
|
43
src/components/RoomDialog.scss
Normal file
43
src/components/RoomDialog.scss
Normal file
@ -0,0 +1,43 @@
|
||||
.RoomDialog-modalButton.is-collaborating {
|
||||
background-color: #ebfbee; // OC GREEN-0
|
||||
color: #2b8a3e; // OC GREEN-9
|
||||
}
|
||||
|
||||
.RoomDialog-linkContainer {
|
||||
display: flex;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.RoomDialog-link {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
margin-left: 1.5em;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
height: 2.5rem;
|
||||
line-height: 2.5rem;
|
||||
padding: 0 0.5rem;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--space-factor);
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.RoomDialog-link:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.RoomDialog-link:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px steelblue;
|
||||
}
|
||||
|
||||
.RoomDialog-sessionStartButtonContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.RoomDialog-stopSession {
|
||||
background-color: #ffe3e3; // OC RED-1
|
||||
color: #c92a2a; // OC RED-9
|
||||
}
|
165
src/components/RoomDialog.tsx
Normal file
165
src/components/RoomDialog.tsx
Normal file
@ -0,0 +1,165 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
import { Island } from "./Island";
|
||||
import { t } from "../i18n";
|
||||
import useIsMobile from "../is-mobile";
|
||||
import { users, clipboard, start, stop } from "./icons";
|
||||
import { Modal } from "./Modal";
|
||||
|
||||
import "./RoomDialog.scss";
|
||||
import { copyTextToSystemClipboard } from "../clipboard";
|
||||
|
||||
function RoomModal({
|
||||
onCloseRequest,
|
||||
activeRoomLink,
|
||||
onRoomCreate,
|
||||
onRoomDestroy,
|
||||
}: {
|
||||
onCloseRequest: () => void;
|
||||
activeRoomLink: string;
|
||||
onRoomCreate: () => void;
|
||||
onRoomDestroy: () => void;
|
||||
}) {
|
||||
const roomLinkInput = useRef<HTMLInputElement>(null);
|
||||
function copyRoomLink() {
|
||||
copyTextToSystemClipboard(activeRoomLink);
|
||||
if (roomLinkInput.current) {
|
||||
roomLinkInput.current.select();
|
||||
}
|
||||
}
|
||||
function selectInput(event: React.MouseEvent<HTMLInputElement>) {
|
||||
if (event.target !== document.activeElement) {
|
||||
event.preventDefault();
|
||||
(event.target as HTMLInputElement).select();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="RoomDialog-modal">
|
||||
<Island padding={4}>
|
||||
<button
|
||||
className="Modal__close"
|
||||
onClick={onCloseRequest}
|
||||
aria-label={t("buttons.close")}
|
||||
>
|
||||
╳
|
||||
</button>
|
||||
<h2 id="export-title">{t("labels.createRoom")}</h2>
|
||||
{!activeRoomLink && (
|
||||
<>
|
||||
<p>{t("roomDialog.desc_intro")}</p>
|
||||
<p>{`🔒 ${t("roomDialog.desc_privacy")}`}</p>
|
||||
<p>{t("roomDialog.desc_start")}</p>
|
||||
<div className="RoomDialog-sessionStartButtonContainer">
|
||||
<ToolButton
|
||||
className="RoomDialog-startSession"
|
||||
type="button"
|
||||
icon={start}
|
||||
title={t("roomDialog.button_startSession")}
|
||||
aria-label={t("roomDialog.button_startSession")}
|
||||
showAriaLabel={true}
|
||||
onClick={onRoomCreate}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeRoomLink && (
|
||||
<>
|
||||
<p>{t("roomDialog.desc_inProgressIntro")}</p>
|
||||
<p>{t("roomDialog.desc_shareLink")}</p>
|
||||
<div className="RoomDialog-linkContainer">
|
||||
<ToolButton
|
||||
type="button"
|
||||
icon={clipboard}
|
||||
title={t("labels.copy")}
|
||||
aria-label={t("labels.copy")}
|
||||
onClick={copyRoomLink}
|
||||
/>
|
||||
<input
|
||||
value={activeRoomLink}
|
||||
readOnly={true}
|
||||
className="RoomDialog-link"
|
||||
ref={roomLinkInput}
|
||||
onPointerDown={selectInput}
|
||||
/>
|
||||
</div>
|
||||
<p>{`🔒 ${t("roomDialog.desc_privacy")}`}</p>
|
||||
<p>
|
||||
<span role="img" aria-hidden="true">
|
||||
⚠️
|
||||
</span>{" "}
|
||||
{t("roomDialog.desc_persistenceWarning")}
|
||||
</p>
|
||||
<p>{t("roomDialog.desc_exitSession")}</p>
|
||||
<div className="RoomDialog-sessionStartButtonContainer">
|
||||
<ToolButton
|
||||
className="RoomDialog-stopSession"
|
||||
type="button"
|
||||
icon={stop}
|
||||
title={t("roomDialog.button_stopSession")}
|
||||
aria-label={t("roomDialog.button_stopSession")}
|
||||
showAriaLabel={true}
|
||||
onClick={onRoomDestroy}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Island>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RoomDialog({
|
||||
isCollaborating,
|
||||
onRoomCreate,
|
||||
onRoomDestroy,
|
||||
}: {
|
||||
isCollaborating: boolean;
|
||||
onRoomCreate: () => void;
|
||||
onRoomDestroy: () => void;
|
||||
}) {
|
||||
const [modalIsShown, setModalIsShown] = useState(false);
|
||||
const [activeRoomLink, setActiveRoomLink] = useState("");
|
||||
|
||||
const triggerButton = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const handleClose = React.useCallback(() => {
|
||||
setModalIsShown(false);
|
||||
triggerButton.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveRoomLink(isCollaborating ? window.location.href : "");
|
||||
}, [isCollaborating]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolButton
|
||||
className={`RoomDialog-modalButton ${
|
||||
isCollaborating ? "is-collaborating" : ""
|
||||
}`}
|
||||
onClick={() => setModalIsShown(true)}
|
||||
icon={users}
|
||||
type="button"
|
||||
title={t("buttons.roomDialog")}
|
||||
aria-label={t("buttons.roomDialog")}
|
||||
showAriaLabel={useIsMobile()}
|
||||
ref={triggerButton}
|
||||
/>
|
||||
{modalIsShown && (
|
||||
<Modal
|
||||
maxWidth={800}
|
||||
labelledBy="room-title"
|
||||
onCloseRequest={handleClose}
|
||||
>
|
||||
<RoomModal
|
||||
onCloseRequest={handleClose}
|
||||
activeRoomLink={activeRoomLink}
|
||||
onRoomCreate={onRoomCreate}
|
||||
onRoomDestroy={onRoomDestroy}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -17,6 +17,7 @@ type ToolButtonBaseProps = {
|
||||
showAriaLabel?: boolean;
|
||||
visible?: boolean;
|
||||
selected?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
type ToolButtonProps =
|
||||
@ -43,7 +44,7 @@ export const ToolButton = React.forwardRef(function(
|
||||
<button
|
||||
className={`ToolIcon_type_button ToolIcon ${sizeCn}${
|
||||
props.selected ? " ToolIcon--selected" : ""
|
||||
}`}
|
||||
} ${props.className || ""}`}
|
||||
title={props.title}
|
||||
aria-label={props["aria-label"]}
|
||||
type="button"
|
||||
|
@ -29,10 +29,15 @@
|
||||
position: relative;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
& + .ToolIcon__label {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ToolIcon__label {
|
||||
font-family: var(--ui-font);
|
||||
margin: 0 0.8em;
|
||||
}
|
||||
|
||||
.ToolIcon_size_s .ToolIcon__icon {
|
||||
|
@ -181,3 +181,26 @@ export const sendToBack = createIcon(
|
||||
</>,
|
||||
24,
|
||||
);
|
||||
|
||||
export const users = createIcon(
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M192 256c61.9 0 112-50.1 112-112S253.9 32 192 32 80 82.1 80 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C51.6 288 0 339.6 0 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zM480 256c53 0 96-43 96-96s-43-96-96-96-96 43-96 96 43 96 96 96zm48 32h-3.8c-13.9 4.8-28.6 8-44.2 8s-30.3-3.2-44.2-8H432c-20.4 0-39.2 5.9-55.7 15.4 24.4 26.3 39.7 61.2 39.7 99.8v38.4c0 2.2-.5 4.3-.6 6.4H592c26.5 0 48-21.5 48-48 0-61.9-50.1-112-112-112z"
|
||||
></path>,
|
||||
640,
|
||||
512,
|
||||
);
|
||||
|
||||
export const start = createIcon(
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm115.7 272l-176 101c-15.8 8.8-35.7-2.5-35.7-21V152c0-18.4 19.8-29.8 35.7-21l176 107c16.4 9.2 16.4 32.9 0 42z"
|
||||
></path>,
|
||||
);
|
||||
|
||||
export const stop = createIcon(
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm96 328c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16h160c8.8 0 16 7.2 16 16v160z"
|
||||
></path>,
|
||||
);
|
||||
|
@ -92,7 +92,7 @@ export function getCollaborationLinkData(link: string) {
|
||||
export async function generateCollaborationLink() {
|
||||
const id = await generateRandomID();
|
||||
const key = await generateEncryptionKey();
|
||||
return `${window.location.href}#room=${id},${key}`;
|
||||
return `${window.location.origin}#room=${id},${key}`;
|
||||
}
|
||||
|
||||
async function getImportedKey(key: string, usage: string): Promise<CryptoKey> {
|
||||
|
@ -41,7 +41,8 @@
|
||||
"canvasBackground": "Canvas background",
|
||||
"drawingCanvas": "Drawing Canvas",
|
||||
"layers": "Layers",
|
||||
"language": "Language"
|
||||
"language": "Language",
|
||||
"createRoom": "Share a live-collaboration session"
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Reset the canvas",
|
||||
@ -62,7 +63,9 @@
|
||||
"done": "Done",
|
||||
"edit": "Edit",
|
||||
"undo": "Undo",
|
||||
"redo": "Redo"
|
||||
"redo": "Redo",
|
||||
"roomDialog": "Share a live-collaboration session",
|
||||
"createNewRoom": "Create new room"
|
||||
},
|
||||
"alerts": {
|
||||
"clearReset": "This will clear the whole canvas. Are you sure?",
|
||||
@ -105,5 +108,16 @@
|
||||
"errorStack": "Error stack trace:",
|
||||
"errorStack_loading": "Loading data. please wait...",
|
||||
"sceneContent": "Scene content:"
|
||||
},
|
||||
"roomDialog": {
|
||||
"desc_intro": "You can invite people to your current scene to collaborate with you.",
|
||||
"desc_privacy": "Don't worry, the session uses end-to-end encryption, so whatever you draw will stay private. Not even our server will be able to see what you come up with.",
|
||||
"desc_start": "To begin, click the button below. (Will use the current scene. If you don't want this, you can manually clear it, first)",
|
||||
"button_startSession": "Start session",
|
||||
"button_stopSession": "Stop session",
|
||||
"desc_inProgressIntro": "Live-collaboration session is now in progress.",
|
||||
"desc_persistenceWarning": "Note that the scene data is shared across collaborators in a P2P fashion, and not persisted to our server. Thus, if all of you disconnect, you will loose the data unless you export it to a file or a shareable link.",
|
||||
"desc_shareLink": "Share this link with anyone you want to collaborate with:",
|
||||
"desc_exitSession": "Stopping the session will disconnect your from the room, but you'll be able to continue working with the scene, locally. Note that this won't affect other people, and they'll still be able to collaborate on their version."
|
||||
}
|
||||
}
|
||||
|
@ -239,10 +239,6 @@ button,
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.App-right-menu {
|
||||
width: 13.75rem;
|
||||
}
|
||||
|
||||
.ErrorSplash {
|
||||
min-height: 100vh;
|
||||
padding: 20px 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user