aboutsummaryrefslogtreecommitdiff
path: root/src/mobile
diff options
context:
space:
mode:
Diffstat (limited to 'src/mobile')
-rw-r--r--src/mobile/AudioUpload.scss24
-rw-r--r--src/mobile/AudioUpload.tsx237
-rw-r--r--src/mobile/ImageUpload.scss125
-rw-r--r--src/mobile/ImageUpload.tsx232
-rw-r--r--src/mobile/MobileHome.scss101
-rw-r--r--src/mobile/MobileInkOverlay.tsx4
-rw-r--r--src/mobile/MobileInterface.scss19
-rw-r--r--src/mobile/MobileInterface.tsx983
-rw-r--r--src/mobile/MobileMain.tsx25
-rw-r--r--src/mobile/MobileMenu.scss440
10 files changed, 1854 insertions, 336 deletions
diff --git a/src/mobile/AudioUpload.scss b/src/mobile/AudioUpload.scss
new file mode 100644
index 000000000..9fe442e55
--- /dev/null
+++ b/src/mobile/AudioUpload.scss
@@ -0,0 +1,24 @@
+@import "../client/views/globalCssVariables.scss";
+
+.audioUpload_cont {
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ max-width: 400px;
+ min-width: 400px;
+}
+
+.audio-upload {
+ top: 100%;
+ opacity: 0;
+}
+
+.audio-upload.active {
+ top: 0;
+ position: absolute;
+ z-index: 999;
+ height: 100vh;
+ width: 100vw;
+ opacity: 1;
+} \ No newline at end of file
diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx
new file mode 100644
index 000000000..7ea11ee84
--- /dev/null
+++ b/src/mobile/AudioUpload.tsx
@@ -0,0 +1,237 @@
+import * as ReactDOM from 'react-dom';
+import * as rp from 'request-promise';
+import { Docs } from '../client/documents/Documents';
+import "./ImageUpload.scss";
+import React = require('react');
+import { DocServer } from '../client/DocServer';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Utils, emptyPath, returnFalse, emptyFunction, returnOne, returnZero, returnTrue } from '../Utils';
+import { Networking } from '../client/Network';
+import { Doc, Opt } from '../fields/Doc';
+import { Cast } from '../fields/Types';
+import { listSpec } from '../fields/Schema';
+import { List } from '../fields/List';
+import { Scripting } from '../client/util/Scripting';
+import MainViewModal from '../client/views/MainViewModal';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { nullAudio } from '../fields/URLField';
+import { Transform } from '../client/util/Transform';
+import { DocumentView } from '../client/views/nodes/DocumentView';
+import { MobileInterface } from './MobileInterface';
+
+export interface ImageUploadProps {
+ Document: Doc;
+}
+
+// const onPointerDown = (e: React.TouchEvent) => {
+// let imgInput = document.getElementById("input_image_file");
+// if (imgInput) {
+// imgInput.click();
+// }
+// }
+const inputRef = React.createRef<HTMLInputElement>();
+
+@observer
+export class AudioUpload extends React.Component {
+ @observable error: string = "";
+ @observable status: string = "";
+ @observable nm: string = "Choose files";
+ @observable process: string = "";
+
+ onClick = async () => {
+ try {
+ await Docs.Prototypes.initialize();
+ const imgPrev = document.getElementById("img_preview");
+ const slab1 = document.getElementById("slab1");
+ if (slab1) {
+ slab1.style.opacity = "1";
+ }
+ if (imgPrev) {
+ const files: FileList | null = inputRef.current!.files;
+ const slab2 = document.getElementById("slab2");
+ if (slab2) {
+ slab2.style.opacity = "1";
+ }
+ if (files && files.length !== 0) {
+ this.process = "Uploading Files";
+ for (let index = 0; index < files.length; ++index) {
+ const file = files[index];
+ const res = await Networking.UploadFilesToServer(file);
+ const slab3 = document.getElementById("slab3");
+ if (slab3) {
+ slab3.style.opacity = "1";
+ }
+ res.map(async ({ result }) => {
+ const name = file.name;
+ if (result instanceof Error) {
+ return;
+ }
+ const path = Utils.prepend(result.accessPaths.agnostic.client);
+ let doc = null;
+ console.log("type: " + file.type);
+ if (file.type === "video/mp4") {
+ doc = Docs.Create.VideoDocument(path, { _nativeWidth: 200, _width: 200, title: name });
+ } else if (file.type === "application/pdf") {
+ doc = Docs.Create.PdfDocument(path, { _width: 200, title: name });
+ } else {
+ doc = Docs.Create.ImageDocument(path, { _nativeWidth: 200, _width: 200, title: name });
+ }
+ const slab4 = document.getElementById("slab4");
+ if (slab4) {
+ slab4.style.opacity = "1";
+ }
+ const res = await rp.get(Utils.prepend("/getUserDocumentId"));
+ if (!res) {
+ throw new Error("No user id returned");
+ }
+ const field = await DocServer.GetRefField(res);
+ let pending: Opt<Doc>;
+ if (field instanceof Doc) {
+ pending = await Cast(field.mobileUpload, Doc);
+ }
+ if (pending) {
+ const data = await Cast(pending.data, listSpec(Doc));
+ if (data) {
+ data.push(doc);
+ } else {
+ pending.data = new List([doc]);
+ }
+ this.status = "finished";
+ const slab5 = document.getElementById("slab5");
+ if (slab5) {
+ slab5.style.opacity = "1";
+ }
+ this.process = "File " + (index + 1).toString() + " Uploaded";
+ const slab6 = document.getElementById("slab6");
+ if (slab6) {
+ slab6.style.opacity = "1";
+ }
+ const slab7 = document.getElementById("slab7");
+ if (slab7) {
+ slab7.style.opacity = "1";
+ }
+
+ }
+ });
+ }
+ } else {
+ this.process = "No file selected";
+ }
+ setTimeout(this.clearUpload, 3000);
+ }
+ } catch (error) {
+ this.error = JSON.stringify(error);
+ }
+ }
+
+ // Updates label after a files is selected (so user knows a file is uploaded)
+ inputLabel = async () => {
+ const files: FileList | null = inputRef.current!.files;
+ await files;
+ if (files && files.length === 1) {
+ console.log(files);
+ this.nm = files[0].name;
+ } else if (files && files.length > 1) {
+ console.log(files.length);
+ this.nm = files.length.toString() + " files selected";
+ }
+ }
+
+ @action
+ clearUpload = () => {
+ const slab1 = document.getElementById("slab1");
+ if (slab1) {
+ slab1.style.opacity = "0.4";
+ }
+ const slab2 = document.getElementById("slab2");
+ if (slab2) {
+ slab2.style.opacity = "0.4";
+ }
+ const slab3 = document.getElementById("slab3");
+ if (slab3) {
+ slab3.style.opacity = "0.4";
+ }
+ const slab4 = document.getElementById("slab4");
+ if (slab4) {
+ slab4.style.opacity = "0.4";
+ }
+ const slab5 = document.getElementById("slab5");
+ if (slab5) {
+ slab5.style.opacity = "0.4";
+ }
+ const slab6 = document.getElementById("slab6");
+ if (slab6) {
+ slab6.style.opacity = "0.4";
+ }
+ const slab7 = document.getElementById("slab7");
+ if (slab7) {
+ slab7.style.
+ opacity = "0.4";
+ }
+ this.nm = "Choose files";
+
+ if (inputRef.current) {
+ inputRef.current.value = "";
+ }
+ this.process = "";
+ console.log(inputRef.current!.files);
+ }
+
+
+
+ private get uploadInterface() {
+ const audioDoc = Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio" }), Doc) as Doc;
+
+ return (
+ <div className="imgupload_cont">
+ <div className="closeUpload" onClick={MobileInterface.Instance.toggleAudio}>
+ <FontAwesomeIcon icon="window-close" size={"lg"} />
+ </div>
+ <DocumentView
+ Document={audioDoc}
+ DataDoc={undefined}
+ LibraryPath={emptyPath}
+ addDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ rootSelected={returnFalse}
+ removeDocument={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={() => 1000}
+ PanelHeight={() => 1000}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={() => "white"}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ );
+ }
+
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
+
+ render() {
+ return (
+ <MainViewModal
+ contents={this.uploadInterface}
+ isDisplayed={true}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
+ }
+
+}
+
+
diff --git a/src/mobile/ImageUpload.scss b/src/mobile/ImageUpload.scss
index eea69b81c..b64aac338 100644
--- a/src/mobile/ImageUpload.scss
+++ b/src/mobile/ImageUpload.scss
@@ -5,8 +5,30 @@
justify-content: center;
flex-direction: column;
align-items: center;
- width: 100vw;
- height: 100vh;
+ max-width: 400px;
+ min-width: 400px;
+
+ .upload_label {
+ font-size: 3em;
+ font-weight: 700;
+ color: white;
+ background-color: black;
+ display: inline-block;
+ margin: 10;
+ width: 100%;
+ border-radius: 10px;
+ }
+
+ .file {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: ltr;
+ }
+
+ .upload_label:hover {
+ background-color: darkred;
+ }
.button_file {
text-align: center;
@@ -17,18 +39,97 @@
font-size: 3em;
}
- .input_file {
- display: none;
+ .inputfile {
+ width: 0.1px;
+ height: 0.1px;
+ opacity: 0;
+ overflow: hidden;
+ position: absolute;
+ z-index: -1;
}
- .upload_label,
- .upload_button {
- background: $dark-color;
- font-size: 500%;
- font-family: $sans-serif;
- text-align: center;
- padding: 5vh;
- margin-bottom: 20px;
+ .inputfile+label {
+ font-size: 3em;
+ font-weight: 700;
color: white;
+ background-color: black;
+ display: inline-block;
+ margin: 10px;
+ margin-top: 30px;
+ width: 100%;
+ border-radius: 10px;
+ }
+
+ .inputfile:focus+label,
+ .inputfile+label:hover {
+ background-color: darkred;
}
+
+ .status {
+ font-size: 2em;
+ }
+
+}
+
+.backgroundUpload {
+ height: 100vh;
+ top: 0;
+ z-index: 999;
+ width: 100vw;
+ position: absolute;
+ background-color: lightgrey;
+ opacity: 0.4;
+}
+
+.image-upload {
+ top: 100%;
+ opacity: 0;
+}
+
+.image-upload.active {
+ top: 0;
+ position: absolute;
+ z-index: 999;
+ height: 100vh;
+ width: 100vw;
+ opacity: 1;
+}
+
+.uploadContainer {
+ top: 40;
+ position: absolute;
+ z-index: 1000;
+ height: 20vh;
+ width: 80vw;
+ opacity: 1;
+}
+
+.closeUpload {
+ position: absolute;
+ border-radius: 10px;
+ top: 3;
+ color: black;
+ font-size: 30;
+ right: 3;
+ z-index: 1002;
+ padding: 0px 3px;
+ background: rgba(0, 0, 0, 0);
+ transition: 0.5s ease all;
+ border: 0px solid;
+}
+
+.loadingImage {
+ display: inline-flex;
+ width: max-content;
+}
+
+.loadingSlab {
+ position: relative;
+ width: 30px;
+ height: 30px;
+ margin: 10;
+ border-radius: 20px;
+ opacity: 0.3;
+ background-color: black;
+ transition: all 2s, opacity 1.5s;
} \ No newline at end of file
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index b15042f9f..b712d52cc 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -4,16 +4,23 @@ import { Docs } from '../client/documents/Documents';
import "./ImageUpload.scss";
import React = require('react');
import { DocServer } from '../client/DocServer';
-import { Opt, Doc } from '../fields/Doc';
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { Utils } from '../Utils';
+import { Networking } from '../client/Network';
+import { Doc, Opt } from '../fields/Doc';
import { Cast } from '../fields/Types';
import { listSpec } from '../fields/Schema';
import { List } from '../fields/List';
-import { observer } from 'mobx-react';
-import { observable } from 'mobx';
-import { Utils } from '../Utils';
-import MobileInterface from './MobileInterface';
+import { Scripting } from '../client/util/Scripting';
+import MainViewModal from '../client/views/MainViewModal';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { MobileInterface } from './MobileInterface';
import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { resolvedPorts } from '../client/views/Main';
+
+export interface ImageUploadProps {
+ Document: Doc;
+}
// const onPointerDown = (e: React.TouchEvent) => {
// let imgInput = document.getElementById("input_image_file");
@@ -24,105 +31,172 @@ import { resolvedPorts } from '../client/views/Main';
const inputRef = React.createRef<HTMLInputElement>();
@observer
-class Uploader extends React.Component {
+export class Uploader extends React.Component<ImageUploadProps> {
@observable error: string = "";
@observable status: string = "";
+ @observable nm: string = "Choose files";
+ @observable process: string = "";
onClick = async () => {
- console.log("uploader click");
try {
- this.status = "initializing protos";
+ const col = this.props.Document;
await Docs.Prototypes.initialize();
const imgPrev = document.getElementById("img_preview");
+ // Slab 1
+ const slab1 = document.getElementById("slab1");
+ if (slab1) slab1.style.opacity = "1";
if (imgPrev) {
const files: FileList | null = inputRef.current!.files;
+ // Slab 2
+ const slab2 = document.getElementById("slab2");
+ if (slab2) slab2.style.opacity = "1";
if (files && files.length !== 0) {
- console.log(files[0]);
- const name = files[0].name;
- const formData = new FormData();
- formData.append("file", files[0]);
-
- const upload = window.location.origin + "/uploadFormData";
- this.status = "uploading image";
- console.log("uploading image", formData);
- const res = await fetch(upload, {
- method: 'POST',
- body: formData
- });
- this.status = "upload image, getting json";
- const json = await res.json();
- json.map(async (file: any) => {
- const path = window.location.origin + file;
- const doc = Docs.Create.ImageDocument(path, { _nativeWidth: 200, _width: 200, title: name });
-
- this.status = "getting user document";
-
- const res = await rp.get(Utils.prepend("/getUserDocumentId"));
- if (!res) {
- throw new Error("No user id returned");
- }
- const field = await DocServer.GetRefField(res);
- let pending: Opt<Doc>;
- if (field instanceof Doc) {
- pending = await Cast(field.rightSidebarCollection, Doc);
- }
- if (pending) {
- this.status = "has pending docs";
- const data = await Cast(pending.data, listSpec(Doc));
- if (data) {
- data.push(doc);
+ this.process = "Uploading Files";
+ for (let index = 0; index < files.length; ++index) {
+ const file = files[index];
+ const res = await Networking.UploadFilesToServer(file);
+ // Slab 3
+ const slab3 = document.getElementById("slab3");
+ if (slab3) slab3.style.opacity = "1";
+ res.map(async ({ result }) => {
+ const name = file.name;
+ if (result instanceof Error) {
+ return;
+ }
+ const path = Utils.prepend(result.accessPaths.agnostic.client);
+ let doc = null;
+ console.log("type: " + file.type);
+ if (file.type === "video/mp4") {
+ doc = Docs.Create.VideoDocument(path, { _nativeWidth: 400, _width: 400, title: name });
+ } else if (file.type === "application/pdf") {
+ doc = Docs.Create.PdfDocument(path, { _nativeWidth: 400, _width: 400, title: name });
} else {
- pending.data = new List([doc]);
+ doc = Docs.Create.ImageDocument(path, { _nativeWidth: 400, _width: 400, title: name });
}
- this.status = "finished";
- }
- });
-
- // console.log(window.location.origin + file[0])
-
- //imgPrev.setAttribute("src", window.location.origin + files[0].name)
+ // Slab 4
+ const slab4 = document.getElementById("slab4");
+ if (slab4) slab4.style.opacity = "1";
+ const res = await rp.get(Utils.prepend("/getUserDocumentId"));
+ if (!res) {
+ throw new Error("No user id returned");
+ }
+ const field = await DocServer.GetRefField(res);
+ let pending: Opt<Doc>;
+ if (field instanceof Doc) {
+ // if (col === Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc) {
+ // pending = await Cast(field.rightSidebarCollection, Doc);
+ // }
+ pending = col;
+ //pending = await Cast(field.col, Doc);
+ }
+ if (pending) {
+ const data = await Cast(pending.data, listSpec(Doc));
+ if (data) data.push(doc);
+ else pending.data = new List([doc]);
+ this.status = "finished";
+ const slab5 = document.getElementById("slab5");
+ if (slab5) slab5.style.opacity = "1";
+ this.process = "File " + (index + 1).toString() + " Uploaded";
+ const slab6 = document.getElementById("slab6");
+ if (slab6) slab6.style.opacity = "1";
+ const slab7 = document.getElementById("slab7");
+ if (slab7) slab7.style.opacity = "1";
+ }
+ });
+ }
+ } else {
+ this.process = "No file selected";
}
+ setTimeout(this.clearUpload, 3000);
}
} catch (error) {
this.error = JSON.stringify(error);
}
}
- render() {
+ // Updates label after a files is selected (so user knows a file is uploaded)
+ inputLabel = async () => {
+ const files: FileList | null = inputRef.current!.files;
+ await files;
+ if (files && files.length === 1) {
+ console.log(files);
+ this.nm = files[0].name;
+ } else if (files && files.length > 1) {
+ console.log(files.length);
+ this.nm = files.length.toString() + " files selected";
+ }
+ }
+
+ @action
+ clearUpload = () => {
+ const slab1 = document.getElementById("slab1");
+ if (slab1) slab1.style.opacity = "0.4";
+ const slab2 = document.getElementById("slab2");
+ if (slab2) slab2.style.opacity = "0.4";
+ const slab3 = document.getElementById("slab3");
+ if (slab3) slab3.style.opacity = "0.4";
+ const slab4 = document.getElementById("slab4");
+ if (slab4) slab4.style.opacity = "0.4";
+ const slab5 = document.getElementById("slab5");
+ if (slab5) slab5.style.opacity = "0.4";
+ const slab6 = document.getElementById("slab6");
+ if (slab6) slab6.style.opacity = "0.4";
+ const slab7 = document.getElementById("slab7");
+ if (slab7) slab7.style.opacity = "0.4";
+ this.nm = "Choose files";
+
+ if (inputRef.current) {
+ inputRef.current.value = "";
+ }
+ this.process = "";
+ console.log(inputRef.current!.files);
+ }
+
+
+
+ private get uploadInterface() {
return (
<div className="imgupload_cont">
- <label htmlFor="input_image_file" className="upload_label">Choose an Image</label>
- <input type="file" accept="image/*" className="input_file" id="input_image_file" ref={inputRef}></input>
- <button onClick={this.onClick} className="upload_button">Upload</button>
+ <div className="closeUpload" onClick={MobileInterface.Instance.toggleUpload}>
+ <FontAwesomeIcon icon="window-close" size={"lg"} />
+ </div>
+ <input type="file" accept="application/pdf, video/*,image/*" className="inputFile" id="input_image_file" ref={inputRef} onChange={this.inputLabel} multiple></input>
+ <label className="file" id="label" htmlFor="input_image_file">{this.nm}</label>
+ <div className="upload_label" onClick={this.onClick}>
+ <FontAwesomeIcon icon="upload" size="sm" />
+ &nbsp; &nbsp; Upload
+ </div>
+ {/* <div onClick={this.onClick} className="upload_button">Upload</div> */}
<img id="img_preview" src=""></img>
- <p>{this.status}</p>
- <p>{this.error}</p>
+ {/* <p>{this.status}</p>
+ <p>{this.error}</p> */}
+ <div className="loadingImage">
+ <div className="loadingSlab" id="slab1" />
+ <div className="loadingSlab" id="slab2" />
+ <div className="loadingSlab" id="slab3" />
+ <div className="loadingSlab" id="slab4" />
+ <div className="loadingSlab" id="slab5" />
+ <div className="loadingSlab" id="slab6" />
+ <div className="loadingSlab" id="slab7" />
+ </div>
+ <p className="status">{this.process}</p>
</div>
);
}
-}
-
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
-// DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, "image upload");
-(async () => {
- const info = await CurrentUserUtils.loadCurrentUser();
- DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts.socket, info.email + "mobile");
- await Docs.Prototypes.initialize();
- if (info.id !== "__guest__") {
- // a guest will not have an id registered
- await CurrentUserUtils.loadUserDocument(info);
+ render() {
+ return (
+ <MainViewModal
+ contents={this.uploadInterface}
+ isDisplayed={true}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
}
- document.getElementById('root')!.addEventListener('wheel', event => {
- if (event.ctrlKey) {
- event.preventDefault();
- }
- }, true);
- ReactDOM.render((
- // <Uploader />
- <MobileInterface />
- ),
- document.getElementById('root')
- );
+
}
-)(); \ No newline at end of file
diff --git a/src/mobile/MobileHome.scss b/src/mobile/MobileHome.scss
new file mode 100644
index 000000000..e1566b622
--- /dev/null
+++ b/src/mobile/MobileHome.scss
@@ -0,0 +1,101 @@
+$navbar-height: 120px;
+$pathbar-height: 50px;
+
+* {
+ margin: 0px;
+ padding: 0px;
+ box-sizing: border-box;
+ font-family: "Open Sans";
+}
+
+.homeContainer {
+ position: relative;
+ top: 200px;
+ overflow: scroll;
+ width: 100%;
+ left: 0;
+ height: calc(100% - 120px);
+ overflow-y: scroll;
+}
+
+.homeButton {
+ width: 96%;
+ margin-left: 2.5%;
+ height: 250px;
+ border-radius: 30px;
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+
+.iconRight {
+ position: absolute;
+ width: 50%;
+ height: 80px;
+ transform: translate(0, 50%);
+ right: 0px;
+ text-align: center;
+ font-size: 80;
+}
+
+.iconLeft {
+ position: absolute;
+ width: 50%;
+ height: 80px;
+ transform: translate(0%, 50%);
+ left: 0px;
+ text-align: center;
+ font-size: 80;
+}
+
+.textLeft {
+ position: absolute;
+ width: 50%;
+ left: 0px;
+ font-size: 40px;
+ text-align: left;
+ margin-left: 110px;
+ margin-top: 40px;
+ font-family: sans-serif;
+ font-weight: bold;
+}
+
+.textRight {
+ position: absolute;
+ width: 50%;
+ right: 0px;
+ font-size: 40px;
+ text-align: right;
+ margin-right: 110px;
+ margin-top: 40px;
+ font-family: sans-serif;
+ font-weight: bold;
+}
+
+.menuView {
+ position: absolute;
+ top: 135px;
+ left: 50%;
+ transform: translate(-50%, 0%);
+ display: flex;
+}
+
+.iconView {
+ height: 60px;
+ width: 60px;
+ background-color: darkgray;
+ border-radius: 5px;
+ border-style: solid;
+ border-width: 2px;
+ border-color: black;
+}
+
+.listView {
+ height: 60px;
+ width: 60px;
+ margin-left: 20;
+ background-color: darkgray;
+ border-radius: 5px;
+ border-style: solid;
+ border-width: 2px;
+ border-color: black;
+} \ No newline at end of file
diff --git a/src/mobile/MobileInkOverlay.tsx b/src/mobile/MobileInkOverlay.tsx
index 59c73ed27..d668d134e 100644
--- a/src/mobile/MobileInkOverlay.tsx
+++ b/src/mobile/MobileInkOverlay.tsx
@@ -4,11 +4,9 @@ import { MobileInkOverlayContent, GestureContent, UpdateMobileInkOverlayPosition
import { observable, action } from "mobx";
import { GestureUtils } from "../pen-gestures/GestureUtils";
import "./MobileInkOverlay.scss";
-import { StrCast, Cast } from '../fields/Types';
import { DragManager } from "../client/util/DragManager";
import { DocServer } from '../client/DocServer';
-import { Doc, DocListCastAsync } from '../fields/Doc';
-import { listSpec } from '../fields/Schema';
+import { Doc } from '../fields/Doc';
@observer
diff --git a/src/mobile/MobileInterface.scss b/src/mobile/MobileInterface.scss
index 4d86e208f..86b043590 100644
--- a/src/mobile/MobileInterface.scss
+++ b/src/mobile/MobileInterface.scss
@@ -16,4 +16,21 @@
height: 100%;
position: relative;
touch-action: none;
-} \ No newline at end of file
+ width: 100%;
+
+ -webkit-touch-callout:none;
+ -webkit-user-select:none;
+ -khtml-user-select:none;
+ -moz-user-select:none;
+ -ms-user-select:none;
+ user-select:none;
+ -webkit-tap-highlight-color:rgba(0,0,0,0);
+}
+
+.mobileInterface-background {
+ height: 100%;
+ width: 100%;
+ position: relative;
+ touch-action: none;
+ background-color: pink;
+}
diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx
index a50ec103e..a1719c015 100644
--- a/src/mobile/MobileInterface.tsx
+++ b/src/mobile/MobileInterface.tsx
@@ -1,47 +1,75 @@
-import React = require('react');
+import * as React from "react";
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faEraser, faHighlighter, faLongArrowAltLeft, faMousePointer, faPenNib } from '@fortawesome/free-solid-svg-icons';
+import {
+ faTasks, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, faFile as fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+ faThumbtack, faTree, faTv, faBook, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft
+} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import * as rp from 'request-promise';
+import { Doc, DocListCast } from '../fields/Doc';
+import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
+import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
import { DocServer } from '../client/DocServer';
-import { Docs } from '../client/documents/Documents';
-import { DocumentManager } from '../client/util/DocumentManager';
-import RichTextMenu from '../client/views/nodes/formattedText/RichTextMenu';
+import { Docs, DocumentOptions } from '../client/documents/Documents';
import { Scripting } from '../client/util/Scripting';
-import { Transform } from '../client/util/Transform';
-import { DocumentDecorations } from '../client/views/DocumentDecorations';
-import GestureOverlay from '../client/views/GestureOverlay';
import { DocumentView } from '../client/views/nodes/DocumentView';
-import { RadialMenu } from '../client/views/nodes/RadialMenu';
-import { PreviewCursor } from '../client/views/PreviewCursor';
-import { Doc, DocListCast, FieldResult } from '../fields/Doc';
-import { Id } from '../fields/FieldSymbols';
+import { Transform } from '../client/util/Transform';
+import "./MobileInterface.scss";
+import "./MobileMenu.scss";
+import "./MobileHome.scss";
+import "./ImageUpload.scss";
+import "./AudioUpload.scss";
+import { DocumentManager } from '../client/util/DocumentManager';
+import SettingsManager from '../client/util/SettingsManager';
+import { Uploader } from "./ImageUpload";
+import { DockedFrameRenderer } from '../client/views/collections/CollectionDockingView';
import { InkTool } from '../fields/InkField';
import { listSpec } from '../fields/Schema';
+import { nullAudio } from '../fields/URLField';
+import GestureOverlay from "../client/views/GestureOverlay";
+import { ScriptField } from "../fields/ScriptField";
+import InkOptionsMenu from "../client/views/collections/collectionFreeForm/InkOptionsMenu";
+import { RadialMenu } from "../client/views/nodes/RadialMenu";
+import { UndoManager, undoBatch } from "../client/util/UndoManager";
+import { MainView } from "../client/views/MainView";
+import { List } from "../fields/List";
+import { AudioUpload } from "./AudioUpload";
import { Cast, FieldValue } from '../fields/Types';
-import { WebField } from "../fields/URLField";
-import { CurrentUserUtils } from '../client/util/CurrentUserUtils';
-import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from '../Utils';
-import "./MobileInterface.scss";
import { CollectionView } from '../client/views/collections/CollectionView';
import { InkingStroke } from '../client/views/InkingStroke';
-library.add(faLongArrowAltLeft);
+library.add(faTasks, faFolderOpen, faAngleDoubleLeft, faExternalLinkSquareAlt, faMobile, faThLarge, faWindowClose, faEdit, faTrashAlt, faPalette, faAngleRight, faBell, faTrash, faCamera, faExpand, faCaretDown, faCaretLeft, faCaretRight, faCaretSquareDown, faCaretSquareRight, faArrowsAltH, faPlus, faMinus,
+ faTerminal, faToggleOn, fileSolid, faExternalLinkAlt, faLocationArrow, faSearch, faFileDownload, faStop, faCalculator, faWindowMaximize, faAddressCard,
+ faQuestionCircle, faArrowLeft, faArrowRight, faArrowDown, faArrowUp, faBolt, faBullseye, faCaretUp, faCat, faCheck, faChevronRight, faClipboard, faClone, faCloudUploadAlt,
+ faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter,
+ faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote,
+ faThumbtack, faTree, faTv, faUndoAlt, faBook, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faHome, faLongArrowAltLeft, faBars, faTh, faChevronLeft);
@observer
-export default class MobileInterface extends React.Component {
+export class MobileInterface extends React.Component {
@observable static Instance: MobileInterface;
@computed private get userDoc() { return Doc.UserDoc(); }
@computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeMobile, Doc)) : CurrentUserUtils.GuestMobile; }
- // @observable private currentView: "main" | "ink" | "upload" = "main";
- private mainDoc: any = CurrentUserUtils.setupMobileDoc(this.userDoc);
+ @observable private mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(this.userDoc);
@observable private renderView?: () => JSX.Element;
+ @observable private sidebarActive: boolean = false; //to toggle sidebar display
+ @observable private imageUploadActive: boolean = false; //to toggle image upload
+ @observable private audioUploadActive: boolean = false;
+ @observable private menuListView: boolean = false; //to switch between menu view (list / icon)
+ @observable private _ink: boolean = false; //toggle whether ink is being dispalyed
- // private inkDoc?: Doc;
- public drawingInk: boolean = false;
-
- // private uploadDoc?: Doc;
+ public _activeDoc: Doc = this.mainDoc; // doc updated as the active mobile page is updated (initially home menu)
+ public _homeDoc: Doc = this.mainDoc; // home menu as a document
+ private _homeMenu: boolean = true; // to determine whether currently at home menu
+ private _child: Doc | null = null; // currently selected document
+ private _parents: Array<Doc> = []; // array of parent docs (for pathbar)
+ private _library: Doc = CurrentUserUtils.setupLibrary(this.userDoc); // to access documents in Dash Web
constructor(props: Readonly<{}>) {
super(props);
@@ -50,298 +78,771 @@ export default class MobileInterface extends React.Component {
@action
componentDidMount = () => {
- library.add(...[faPenNib, faHighlighter, faEraser, faMousePointer]);
+ Doc.UserDoc().activeMobile = this._homeDoc;
+ this._homeDoc._viewType === "stacking" ? this.menuListView = true : this.menuListView = false;
+ Doc.SetSelectedTool(InkTool.None);
+ this.switchCurrentView((userDoc: Doc) => this._homeDoc);
- if (this.userDoc && !this.mainContainer) {
- this.userDoc.activeMobile = this.mainDoc;
- }
+ document.removeEventListener("dblclick", this.onReactDoubleClick);
+ document.addEventListener("dblclick", this.onReactDoubleClick);
}
@action
+ componentWillUnmount = () => {
+ document.removeEventListener('dblclick', this.onReactDoubleClick);
+ }
+
+ // Prevent zooming in when double tapping the screen
+ onReactDoubleClick = (e: MouseEvent) => {
+ e.stopPropagation();
+ }
+
+ // Switch the mobile view to the given doc
+ @action
switchCurrentView = (doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) => {
if (!this.userDoc) return;
- this.userDoc.activeMobile = doc(this.userDoc);
+ Doc.UserDoc().activeMobile = doc(this.userDoc);
onSwitch && onSwitch();
this.renderView = renderView;
}
- onSwitchInking = () => {
- Doc.SetSelectedTool(InkTool.Pen);
- MobileInterface.Instance.drawingInk = true;
+ // For toggling the hamburger menu
+ @action
+ toggleSidebar = () => this.sidebarActive = !this.sidebarActive
- DocServer.Mobile.dispatchOverlayTrigger({
- enableOverlay: true,
- width: window.innerWidth,
- height: window.innerHeight
- });
+ /**
+ * Method called when 'Library' button is pressed on the home screen
+ */
+ switchToLibrary = () => {
+ this._parents.push(this._activeDoc);
+ this.switchCurrentView((userDoc: Doc) => this._library);
+ this._activeDoc = this._library;
+ this._homeMenu = false;
+ this.sidebarActive = true;
+ }
+
+ openWorkspaces = () => {
+ this._parents.push(this._activeDoc);
+ this.switchCurrentView((userDoc: Doc) => this._library);
+ this._activeDoc = this._library;
+ this._homeMenu = false;
}
- onSwitchUpload = async () => {
- let width = 300;
- let height = 300;
+ /**
+ * Back method for navigating through items
+ */
+ back = () => {
+ const header = document.getElementById("header") as HTMLElement;
+ const doc = Cast(this._parents.pop(), Doc) as Doc;
- // get width and height of the collection doc
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
- const docView = DocumentManager.Instance.getDocumentView(collectionDoc);
- if (docView) {
- width = docView.nativeWidth ? docView.nativeWidth : 300;
- height = docView.nativeHeight ? docView.nativeHeight : 300;
- }
+ if (doc === Cast(this._library, Doc) as Doc) {
+ this._child = null;
+ this.userDoc.activeMobile = this._library;
+ } else if (doc === Cast(this._homeDoc, Doc) as Doc) {
+ this._homeMenu = true;
+ this._parents = [];
+ this._activeDoc = this._homeDoc;
+ this._child = null;
+ this.switchCurrentView((userDoc: Doc) => this._homeDoc);
+ } else {
+ if (doc) {
+ this._child = doc;
+ this.switchCurrentView((userDoc: Doc) => doc);
+ this._homeMenu = false;
+ header.textContent = String(doc.title);
}
}
- DocServer.Mobile.dispatchOverlayTrigger({
- enableOverlay: true,
- width: width,
- height: height,
- text: "Documents uploaded from mobile will show here",
- });
- }
-
- renderDefaultContent = () => {
- if (this.mainContainer) {
- return <DocumentView
- Document={this.mainContainer}
- DataDoc={undefined}
- LibraryPath={emptyPath}
- addDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- rootSelected={returnFalse}
- removeDocument={undefined}
- onClick={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={returnOne}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={() => window.screen.width}
- PanelHeight={() => window.screen.height}
- renderDepth={0}
- focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />;
+ if (doc) {
+ this._activeDoc = doc;
}
- return "hello";
+ this._ink = false;
}
- onBack = (e: React.MouseEvent) => {
- this.switchCurrentView((userDoc: Doc) => this.mainDoc);
- Doc.SetSelectedTool(InkTool.None); // TODO: switch to previous tool
-
- DocServer.Mobile.dispatchOverlayTrigger({
- enableOverlay: false,
- width: window.innerWidth,
- height: window.innerHeight
- });
-
- // this.inkDoc = undefined;
- this.drawingInk = false;
- }
-
- shiftLeft = (e: React.MouseEvent) => {
- DocServer.Mobile.dispatchOverlayPositionUpdate({
- dx: -10
- });
- e.preventDefault();
- e.stopPropagation();
+ /**
+ * Return 'Home", which implies returning to 'Home' buttons
+ */
+ returnHome = () => {
+ if (!this._homeMenu || this.sidebarActive) {
+ this._homeMenu = true;
+ this._parents = [];
+ this._activeDoc = this._homeDoc;
+ this._child = null;
+ this.switchCurrentView((userDoc: Doc) => this._homeDoc);
+ }
+ if (this.sidebarActive) {
+ this.toggleSidebar();
+ }
}
- shiftRight = (e: React.MouseEvent) => {
- DocServer.Mobile.dispatchOverlayPositionUpdate({
- dx: 10
- });
- e.preventDefault();
- e.stopPropagation();
+ /**
+ * Return to primary Workspace in library (Workspaces Doc)
+ */
+ returnMain = () => {
+ this._parents = [this._homeDoc];
+ this._activeDoc = this._library;
+ this.switchCurrentView((userDoc: Doc) => this._library);
+ this._homeMenu = false;
+ this._child = null;
}
- panelHeight = () => window.innerHeight;
- panelWidth = () => window.innerWidth;
- renderInkingContent = () => {
- console.log("rendering inking content");
- // TODO: support panning and zooming
- // TODO: handle moving of ink strokes
+ /**
+ * DocumentView for graphic display of all documents
+ */
+ displayWorkspaces = () => {
if (this.mainContainer) {
+ const backgroundColor = () => "white";
return (
- <div className="mobileInterface">
- <div className="mobileInterface-inkInterfaceButtons">
- <div className="navButtons">
- <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing">BACK</button>
- </div>
- <div className="inkSettingButtons">
- <button className="mobileInterface-button cancel" onClick={this.onBack} title="Cancel drawing"><FontAwesomeIcon icon="long-arrow-alt-left" /></button>
- </div>
- <div className="navButtons">
- <button className="mobileInterface-button" onClick={this.shiftLeft} title="Shift left">left</button>
- <button className="mobileInterface-button" onClick={this.shiftRight} title="Shift right">right</button>
- </div>
- </div>
- <CollectionView
+ <div style={{ position: "relative", top: '198px', height: `calc(100% - 350px)`, width: "100%", left: "0%" }}>
+ <DocumentView
Document={this.mainContainer}
DataDoc={undefined}
LibraryPath={emptyPath}
- filterAddDocument={returnTrue}
- fieldKey={""}
- dropAction={"alias"}
- bringToFront={emptyFunction}
+ addDocument={returnFalse}
addDocTab={returnFalse}
pinToPres={emptyFunction}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
+ rootSelected={returnFalse}
+ removeDocument={undefined}
+ onClick={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={this.returnWidth}
+ PanelHeight={this.returnHeight}
NativeHeight={returnZero}
NativeWidth={returnZero}
- focus={emptyFunction}
- isSelected={returnFalse}
- select={emptyFunction}
- active={returnFalse}
- ContentScaling={returnOne}
- whenActiveChanged={returnFalse}
- ScreenToLocalTransform={Transform.Identity}
renderDepth={0}
+ focus={emptyFunction}
+ backgroundColor={backgroundColor}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
docFilters={returnEmptyFilter}
ContainingCollectionView={undefined}
ContainingCollectionDoc={undefined}
- rootSelected={returnTrue}>
- </CollectionView>
+ />
</div>
);
}
}
- upload = async (e: React.MouseEvent) => {
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const collectionDoc = await data[1]; // this should be the collection doc since the positions should be locked
- const children = DocListCast(collectionDoc.data);
- const uploadDoc = children.length === 1 ? children[0] : Docs.Create.StackingDocument(children, {
- title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true, _width: 300, _height: 300
- });
- if (uploadDoc) {
- DocServer.Mobile.dispatchMobileDocumentUpload({
- docId: uploadDoc[Id],
- });
- }
+ /**
+ * Note: window.innerWidth and window.screen.width compute different values.
+ * window.screen.width is the display size, however window.innerWidth is the
+ * display resolution which computes differently.
+ */
+ returnWidth = () => window.innerWidth; //The windows width
+ returnHeight = () => (window.innerHeight - 300); //Calculating the windows height (-300 to account for topbar)
+
+ /**
+ * Handles the click functionality in the library panel.
+ * Navigates to the given doc and updates the sidebar.
+ * @param doc: doc for which the method is called
+ */
+ @undoBatch
+ handleClick = async (doc: Doc) => {
+ const children = DocListCast(doc.data);
+ if (doc.type !== "collection" && this.sidebarActive) this.openFromSidebar(doc);
+ else if (doc.type === "collection" && children.length === 0) this.openFromSidebar(doc);
+ else {
+ this._parents.push(this._activeDoc);
+ this._activeDoc = doc;
+ this.switchCurrentView((userDoc: Doc) => doc);
+ this._homeMenu = false;
+ this._child = doc;
+ }
+ }
+
+ openFromSidebar = (doc: Doc) => {
+ this._parents.push(this._activeDoc);
+ this._activeDoc = doc;
+ this.switchCurrentView((userDoc: Doc) => doc);
+ this._homeMenu = false;
+ this._child = doc; //?
+ this.toggleSidebar();
+ }
+
+ /**
+ * Handles creation of array which is then rendered in renderPathbar()
+ */
+ createPathname = () => {
+ const docArray = [];
+ this._parents.map((doc: Doc, index: any) => {
+ docArray.push(doc);
+ });
+ docArray.push(this._activeDoc);
+ return docArray;
+ }
+
+ // Renders the graphical pathbar
+ renderPathbar = () => {
+ const docArray = this.createPathname();
+ const items = docArray.map((doc: Doc, index: any) => {
+ if (index === 0) {
+ return (
+ <>
+ {this._homeMenu ?
+ <div className="pathbarItem">
+ <div className="pathbarText"
+ style={{ backgroundColor: "rgb(119, 37, 37)" }}
+ key={index}
+ onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+ </div>
+ </div>
+ :
+ <div className="pathbarItem">
+ <div className="pathbarText"
+ key={index}
+ onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+ </div>
+ </div>}
+ </>);
+
+ } else if (doc === this._activeDoc) {
+ return (
+ <div className="pathbarItem">
+ <FontAwesomeIcon className="pathIcon" icon="angle-right" size="lg" />
+ <div className="pathbarText"
+ style={{ backgroundColor: "rgb(119, 37, 37)" }}
+ key={index}
+ onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+ </div>
+ </div>);
+ } else {
+ return (
+ <div className="pathbarItem">
+ <FontAwesomeIcon className="pathIcon" icon="angle-right" size="lg" />
+ <div className="pathbarText"
+ key={index}
+ onClick={() => this.handlePathClick(doc, index)}>{doc.title}
+ </div>
+ </div>);
}
+
+ });
+ if (this._parents.length !== 0) {
+ return (<div className="pathbar">
+ <div className="scrollmenu">
+ {items}
+ </div>
+ <div className="back" >
+ <FontAwesomeIcon onClick={this.back} icon={"chevron-left"} color="white" size={"2x"} />
+ </div>
+ <div className="hidePath" />
+ </div>);
+ } else {
+ return (<div className="pathbar">
+ <div className="scrollmenu">
+ {items}
+ </div>
+ <div className="hidePath" />
+ </div>);
}
- e.stopPropagation();
- e.preventDefault();
}
- addWebToCollection = async () => {
- let url = "https://en.wikipedia.org/wiki/Hedgehog";
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const webDoc = await data[0];
- const urlField: FieldResult<WebField> = Cast(webDoc.data, WebField);
- url = urlField ? urlField.url.toString() : "https://en.wikipedia.org/wiki/Hedgehog";
+ // Handles when user clicks on a document in the pathbar
+ handlePathClick = (doc: Doc, index: number) => {
+ if (doc === this._library) {
+ this._activeDoc = doc;
+ this._child = null;
+ this.switchCurrentView((userDoc: Doc) => doc);
+ this._parents.length = index;
+ } else if (doc === this._homeDoc) {
+ this.returnHome();
+ } else {
+ this._activeDoc = doc;
+ this._child = doc;
+ this.switchCurrentView((userDoc: Doc) => doc);
+ this._parents.length = index;
+ }
+ }
+
+ // Renders the contents of the menu and sidebar
+ renderDefaultContent = () => {
+ if (this._homeMenu) {
+ return (
+ <div>
+ <div className="navbar">
+ <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
+ <div className="header" id="header">{this._homeDoc.title}</div>
+ <div className="cover" id="cover" onClick={(e) => this.stop(e)}></div>
+ <div className="toggle-btn" id="menuButton" onClick={this.toggleSidebar}>
+ <span></span>
+ <span></span>
+ <span></span>
+ </div>
+ </div>
+ {this.renderPathbar()}
+ </div>
+ );
+ }
+ let workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
+ if (this._child) {
+ workspaces = this._child;
+ }
+
+ const buttons = DocListCast(workspaces.data).map((doc: Doc, index: any) => {
+ if (doc.type !== "ink") {
+ return (
+ <div
+ className="item"
+ key={index}
+ onClick={() => this.handleClick(doc)}>
+ <div className="item-title"> {doc.title} </div>
+ <div className="item-type">{doc.type}</div>
+ <FontAwesomeIcon className="right" icon="angle-right" size="lg" />
+ <FontAwesomeIcon className="open" onClick={() => this.openFromSidebar(doc)} icon="external-link-alt" size="lg" />
+ </div>);
}
+ });
+
+ return (
+ <div>
+ <div className="navbar">
+ <FontAwesomeIcon className="home" icon="home" onClick={this.returnHome} />
+ <div className="header" id="header">{this.sidebarActive ? "library" : this._activeDoc.title}</div>
+ <div className={`toggle-btn ${this.sidebarActive ? "active" : ""}`} onClick={this.toggleSidebar}>
+ <span></span>
+ <span></span>
+ <span></span>
+ </div>
+ </div>
+ {this.renderPathbar()}
+ <div className={`sidebar ${this.sidebarActive ? "active" : ""}`}>
+ <div className="sidebarButtons">
+ {this._child ?
+ <>
+ {buttons}
+ <div
+ className="item" key="home"
+ onClick={this.returnMain}
+ style={{ opacity: 0.7 }}>
+ <FontAwesomeIcon className="right" icon="angle-double-left" size="lg" />
+ <div className="item-type">Return to workspaces</div>
+ </div>
+ </> :
+ <>
+ {buttons}
+ <div
+ className="item"
+ style={{ opacity: 0.7 }}
+ onClick={() => this.createNewWorkspace()}>
+ <FontAwesomeIcon className="right" icon="plus" size="lg" />
+ <div className="item-type">Create New Workspace</div>
+ </div>
+ </>
+ }
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ /**
+ * Handles the Create New Workspace button in the menu
+ */
+ @action
+ createNewWorkspace = async (id?: string) => {
+ const workspaces = Cast(this.userDoc.myWorkspaces, Doc) as Doc;
+ const workspaceCount = DocListCast(workspaces.data).length + 1;
+ const freeformOptions: DocumentOptions = {
+ x: 0,
+ y: 400,
+ title: "Collection " + workspaceCount,
+ _LODdisable: true
+ };
+ const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions);
+ const workspaceDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600, path: [Doc.UserDoc().myCatalog as Doc] }], { title: `Workspace ${workspaceCount}` }, id, "row");
+
+ const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`);
+ const toggleComic = ScriptField.MakeScript(`toggleComicMode()`);
+ const cloneWorkspace = ScriptField.MakeScript(`cloneWorkspace()`);
+ workspaceDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, cloneWorkspace!]);
+ workspaceDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "New Workspace Layout"]);
+
+ Doc.AddDocToList(workspaces, "data", workspaceDoc);
+ // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+ }
+
+ stop = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ }
+
+ // Button for uploading mobile audio
+ uploadAudioButton = () => {
+ if (this._activeDoc.type === "audio") {
+ return <div className="docButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
+ style={{ backgroundColor: "black", color: "white" }}
+ // onClick={this.uploadAudio}
+ >
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload"
+ />
+ </div>;
}
- Docs.Create.WebDocument(url, { _width: 300, _height: 300, title: "Mobile Upload Web Doc" });
}
- clearUpload = async () => {
- if (this.mainContainer) {
- const data = Cast(this.mainContainer.data, listSpec(Doc));
- if (data) {
- const collectionDoc = await data[1];
- const children = DocListCast(collectionDoc.data);
- children.forEach(doc => {
- });
- // collectionDoc[data] = new List<Doc>();
+ // Button for switching between pen and ink mode
+ @action
+ onSwitchInking = () => {
+ const button = document.getElementById("inkButton") as HTMLElement;
+ button.style.backgroundColor = this._ink ? "white" : "black";
+ button.style.color = this._ink ? "black" : "white";
+
+ if (!this._ink) {
+ Doc.SetSelectedTool(InkTool.Pen);
+ this._ink = true;
+ } else {
+ Doc.SetSelectedTool(InkTool.None);
+ this._ink = false;
+ }
+ }
+
+ // The static ink menu that appears at the top
+ inkMenu = () => {
+ if (this._activeDoc._viewType === "docking") {
+ if (this._ink) {
+ console.log("here");
+ return <div className="colorSelector">
+ <InkOptionsMenu />
+ </div>;
+
}
}
}
- renderUploadContent() {
- if (this.mainContainer) {
+
+ undo = () => {
+ if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && this._activeDoc.title !== "WORKSPACES") {
+ return (<>
+ <div className="docButton"
+ style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanUndo() ? "1" : "0.4", }}
+ id="undoButton"
+ title="undo"
+ onClick={(e: React.MouseEvent) => {
+ UndoManager.Undo();
+ e.stopPropagation();
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="undo-alt" />
+ </div>
+ </>);
+ }
+ }
+
+ redo = () => {
+ if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && this._activeDoc.title !== "WORKSPACES") {
+ return (<>
+ <div className="docButton"
+ style={{ backgroundColor: "black", color: "white", fontSize: "60", opacity: UndoManager.CanRedo() ? "1" : "0.4", }}
+ id="undoButton"
+ title="redo"
+ onClick={(e: React.MouseEvent) => {
+ UndoManager.Redo();
+ e.stopPropagation();
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="redo-alt" />
+ </div>
+ </>);
+ }
+ }
+
+ // Button for switching into ink mode
+ drawInk = () => {
+ if (this._activeDoc._viewType === "docking") {
return (
- <div className="mobileInterface" onDragOver={this.onDragOver}>
- <div className="mobileInterface-inkInterfaceButtons">
- <button className="mobileInterface-button cancel" onClick={this.onBack} title="Back">BACK</button>
- {/* <button className="mobileInterface-button" onClick={this.clearUpload} title="Clear Upload">CLEAR</button> */}
- {/* <button className="mobileInterface-button" onClick={this.addWeb} title="Add Web Doc to Upload Collection"></button> */}
- <button className="mobileInterface-button" onClick={this.upload} title="Upload">UPLOAD</button>
+ <>
+ <div className="docButton"
+ id="inkButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
+ onClick={this.onSwitchInking}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="pen-nib"
+ />
+ </div>
+ </>);
+ }
+ }
+
+ // Mobile doc button for uploading
+ upload = () => {
+ if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc) {
+ return (
+ <>
+ <div className="docButton"
+ id="uploadButton"
+ title={"uploadFile"}
+ onClick={this.toggleUpload}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload"
+ />
+ </div>
+ </>);
+ }
+ }
+
+ // Button to download images on the mobile
+ downloadDocument = () => {
+ if (this._activeDoc.type === "image" || this._activeDoc.type === "pdf" || this._activeDoc.type === "video") {
+ const url = this._activeDoc["data-path"]?.toString();
+ return <div className="docButton"
+ title={"Download Image"}
+ style={{ backgroundColor: "white", color: "black" }}
+ onClick={e => {
+ window.open(url);
+ console.log(url);
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="download"
+ />
+ </div>;
+ }
+ }
+
+ // Mobile audio doc
+ recordAudio = async () => {
+ // upload to server with known URL
+ if (this._activeDoc.title !== "mobile audio") {
+ this._parents.push(this._activeDoc);
+ }
+ const audioDoc = Cast(Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "mobile audio" }), Doc) as Doc;
+ if (audioDoc) {
+ this._activeDoc = audioDoc;
+ this.switchCurrentView((userDoc: Doc) => audioDoc);
+ this._homeMenu = false;
+ }
+ }
+
+ // // Pushing the audio doc onto Dash Web through the right side bar
+ // uploadAudio = () => {
+ // const audioRightSidebar = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ // const audioDoc = this._activeDoc;
+ // const data = Cast(audioRightSidebar.data, listSpec(Doc));
+
+ // if (data) {
+ // data.push(audioDoc);
+ // }
+ // }
+
+ // Button for pinning images to presentation
+ pinToPresentation = () => {
+ // Only making button available if it is an image
+ if (!(this._activeDoc.type === "collection" || this._activeDoc.type === "presentation")) {
+ const isPinned = this._activeDoc && Doc.isDocPinned(this._activeDoc);
+ return <div className="docButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Unpin from presentation" : "Pin to presentation"}
+ style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }}
+ onClick={e => {
+ if (isPinned) {
+ DockedFrameRenderer.UnpinDoc(this._activeDoc);
+ }
+ else {
+ DockedFrameRenderer.PinDoc(this._activeDoc);
+ }
+ }}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin"
+ />
+ </div>;
+ }
+ }
+
+ // Buttons for switching the menu between large and small icons
+ switchMenuView = () => {
+ if (this._activeDoc.title === this._homeDoc.title) {
+ return (
+ <div className="homeSwitch">
+ <div className={`list ${!this.menuListView ? "active" : ""}`} onClick={this.changeToIconView}>
+ <FontAwesomeIcon size="sm" icon="th-large" />
+ </div>
+ <div className={`list ${this.menuListView ? "active" : ""}`} onClick={this.changeToListView}>
+ <FontAwesomeIcon size="sm" icon="bars" />
</div>
- <DocumentView
- Document={this.mainContainer}
- DataDoc={undefined}
- LibraryPath={emptyPath}
- addDocument={returnFalse}
- addDocTab={returnFalse}
- pinToPres={emptyFunction}
- rootSelected={returnFalse}
- removeDocument={undefined}
- onClick={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={returnOne}
- NativeHeight={returnZero}
- NativeWidth={returnZero}
- PanelWidth={() => window.screen.width}
- PanelHeight={() => window.screen.height}
- renderDepth={0}
- focus={emptyFunction}
- backgroundColor={returnEmptyString}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
</div>
);
}
}
+ // Logic for switching the menu into the icons
+ @action
+ changeToIconView = () => {
+ if (this._homeDoc._viewType = "stacking") {
+ this.menuListView = false;
+ this._homeDoc._viewType = "masonry";
+ this._homeDoc.columnWidth = 300;
+ const menuButtons = DocListCast(this._homeDoc.data);
+ console.log('hello');
+ menuButtons.map((doc: Doc, index: any) => {
+ console.log(index);
+ const buttonData = DocListCast(doc.data);
+ buttonData[1]._nativeWidth = 0.1;
+ buttonData[1]._width = 0.1;
+ buttonData[1]._dimMagnitude = 0;
+ buttonData[1]._opacity = 0;
+ console.log(buttonData);
+ console.log(doc._nativeWidth);
+ doc._nativeWidth = 400;
+ console.log(doc._nativeWidth);
+ });
+ }
+ }
+
+ // Logic for switching the menu into the stacking view
+ @action
+ changeToListView = () => {
+ if (this._homeDoc._viewType = "masonry") {
+ this._homeDoc._viewType = "stacking";
+ this.menuListView = true;
+ const menuButtons = DocListCast(this._homeDoc.data);
+ console.log('hello');
+ menuButtons.map((doc: Doc, index: any) => {
+ const buttonData = DocListCast(doc.data);
+ buttonData[1]._nativeWidth = 450;
+ buttonData[1]._dimMagnitude = 2;
+ buttonData[1]._opacity = 1;
+ console.log(doc._nativeWidth);
+ doc._nativeWidth = 900;
+ console.log(doc._nativeWidth);
+ });
+ }
+ }
+
+ // For setting up the presentation document for the home menu
+ setupDefaultPresentation = () => {
+ if (this._activeDoc.title !== "Presentation") {
+ this._parents.push(this._activeDoc);
+ }
+
+ const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc;
+
+ if (presentation) {
+ this._activeDoc = presentation;
+ this.switchCurrentView((userDoc: Doc) => presentation);
+ this._homeMenu = false;
+ }
+ }
+
+ // For toggling image upload pop up
+ @action
+ toggleUpload = () => this.imageUploadActive = !this.imageUploadActive
+
+ // For toggling image upload pop up
+ @action
+ toggleAudio = () => this.audioUploadActive = !this.audioUploadActive
+
+ @action
+ toggleUploadInCollection = () => {
+ const button = document.getElementById("imageButton") as HTMLElement;
+ button.style.backgroundColor = this.imageUploadActive ? "white" : "black";
+ button.style.color = this.imageUploadActive ? "black" : "white";
+
+ this.imageUploadActive = !this.imageUploadActive;
+ }
+
+ // For closing the image upload pop up
+ @action
+ closeUpload = () => {
+ this.imageUploadActive = false;
+ }
+
+ // Returns the image upload pop up
+ uploadImage = () => {
+ if (this.imageUploadActive) {
+ console.log("active");
+ } else if (!this.imageUploadActive) {
+
+ }
+
+ let doc;
+ let toggle;
+ if (this._homeMenu === false) {
+ doc = this._activeDoc;
+ toggle = this.toggleUploadInCollection;
+ } else {
+ doc = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ toggle = this.toggleUpload;
+ }
+ return (
+ <div>
+ <div className="closeUpload" onClick={toggle}>
+ <FontAwesomeIcon icon="window-close" size={"lg"} />
+ </div>
+ <Uploader Document={doc} />
+ </div>
+ );
+ }
+
+ displayRadialMenu = () => {
+ if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc) {
+ return <RadialMenu />;
+ }
+ }
+
onDragOver = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
}
+ uploadImageButton = () => {
+ if (this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && this._activeDoc._viewType !== "docking" && this._activeDoc.title !== "WORKSPACES") {
+ return <div className="docButton"
+ id="imageButton"
+ title={Doc.isDocPinned(this._activeDoc) ? "Pen on" : "Pen off"}
+ onClick={this.toggleUpload}>
+ <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="upload"
+ />
+ </div>;
+ }
+ }
+
+ switchToMobileUploads = () => {
+ if (this._activeDoc.title !== "Presentation") {
+ this._parents.push(this._activeDoc);
+ }
+ const mobileUpload = Cast(Doc.UserDoc().rightSidebarCollection, Doc) as Doc;
+ console.log(mobileUpload.title);
+ this._activeDoc = mobileUpload;
+ this.switchCurrentView((userDoc: Doc) => mobileUpload);
+ this._homeMenu = false;
+ }
+
render() {
- // const content = this.currentView === "main" ? this.mainContent :
- // this.currentView === "ink" ? this.inkContent :
- // this.currentView === "upload" ? this.uploadContent : <></>;
return (
<div className="mobileInterface-container" onDragOver={this.onDragOver}>
- {/* <DocumentDecorations />
- <GestureOverlay>
- {this.renderView ? this.renderView() : this.renderDefaultContent()}
- </GestureOverlay> */}
-
- {/* <DictationOverlay />
- <SharingManager />
- <GoogleAuthenticationManager /> */}
- <DocumentDecorations />
+ <SettingsManager />
+ <div className={`image-upload ${this.imageUploadActive ? "active" : ""}`}>
+ {this.uploadImage()}
+ </div>
+ <div className={`audio-upload ${this.audioUploadActive ? "active" : ""}`}>
+ <AudioUpload />
+ </div>
+ {this.switchMenuView()}
+ {this.inkMenu()}
<GestureOverlay>
- {this.renderView ? this.renderView() : this.renderDefaultContent()}
+ <div className="docButtonContainer">
+ {this.pinToPresentation()}
+ {this.downloadDocument()}
+ {this.undo()}
+ {this.drawInk()}
+ {this.redo()}
+ {/* {this.upload()} */}
+ {this.uploadImageButton()}
+ {/* {this.uploadAudioButton()} */}
+ </div>
+ {this.displayWorkspaces()}
+ {this.renderDefaultContent()}
</GestureOverlay>
- <PreviewCursor />
- {/* <ContextMenu /> */}
- <RadialMenu />
- <RichTextMenu />
- {/* <PDFMenu />
- <MarqueeOptionsMenu />
- <OverlayView /> */}
+ {this.displayRadialMenu()}
</div>
);
}
}
-Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); });
-Scripting.addGlobal(function onSwitchMobileInking() { return MobileInterface.Instance.onSwitchInking(); });
-Scripting.addGlobal(function renderMobileInking() { return MobileInterface.Instance.renderInkingContent(); });
-Scripting.addGlobal(function onSwitchMobileUpload() { return MobileInterface.Instance.onSwitchUpload(); });
-Scripting.addGlobal(function renderMobileUpload() { return MobileInterface.Instance.renderUploadContent(); });
-Scripting.addGlobal(function addWebToMobileUpload() { return MobileInterface.Instance.addWebToCollection(); });
+
+Scripting.addGlobal(function switchMobileView(doc: (userDoc: Doc) => Doc, renderView?: () => JSX.Element, onSwitch?: () => void) { return MobileInterface.Instance.switchCurrentView(doc, renderView, onSwitch); },
+ "changes the active document displayed on the mobile, (doc: any)");
+Scripting.addGlobal(function openMobilePresentation() { return MobileInterface.Instance.setupDefaultPresentation(); },
+ "opens the presentation on mobile");
+Scripting.addGlobal(function toggleMobileSidebar() { return MobileInterface.Instance.toggleSidebar(); });
+Scripting.addGlobal(function openMobileAudio() { return MobileInterface.Instance.toggleAudio(); });
+Scripting.addGlobal(function openMobileSettings() { return SettingsManager.Instance.open(); });
+Scripting.addGlobal(function openWorkspaces() { return MobileInterface.Instance.openWorkspaces(); });
+Scripting.addGlobal(function uploadImageMobile() { return MobileInterface.Instance.toggleUpload(); });
+Scripting.addGlobal(function switchToMobileUploads() { return MobileInterface.Instance.switchToMobileUploads(); });
diff --git a/src/mobile/MobileMain.tsx b/src/mobile/MobileMain.tsx
new file mode 100644
index 000000000..3d4680d58
--- /dev/null
+++ b/src/mobile/MobileMain.tsx
@@ -0,0 +1,25 @@
+import { MobileInterface } from "./MobileInterface";
+import { Docs } from "../client/documents/Documents";
+import { CurrentUserUtils } from "../client/util/CurrentUserUtils";
+import * as ReactDOM from 'react-dom';
+import * as React from 'react';
+import { DocServer } from "../client/DocServer";
+import { AssignAllExtensions } from "../extensions/General/Extensions";
+
+AssignAllExtensions();
+
+(async () => {
+ const info = await CurrentUserUtils.loadCurrentUser();
+ DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email + " (mobile)");
+ await Docs.Prototypes.initialize();
+ if (info.id !== "__guest__") {
+ // a guest will not have an id registered
+ await CurrentUserUtils.loadUserDocument(info);
+ }
+ document.getElementById('root')!.addEventListener('wheel', event => {
+ if (event.ctrlKey) {
+ event.preventDefault();
+ }
+ }, true);
+ ReactDOM.render(<MobileInterface />, document.getElementById('root'));
+})(); \ No newline at end of file
diff --git a/src/mobile/MobileMenu.scss b/src/mobile/MobileMenu.scss
new file mode 100644
index 000000000..53bc48034
--- /dev/null
+++ b/src/mobile/MobileMenu.scss
@@ -0,0 +1,440 @@
+$navbar-height: 120px;
+$pathbar-height: 50px;
+
+* {
+ margin: 0px;
+ padding: 0px;
+ box-sizing: border-box;
+ font-family: "Open Sans";
+}
+
+body {
+ overflow: hidden;
+}
+
+.navbar {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ width: 100vw;
+ height: $navbar-height;
+ background-color: whitesmoke;
+ z-index: 150;
+}
+
+.navbar .cover {
+ position: absolute;
+ right: 20px;
+ top: 30px;
+ height: 70px;
+ width: 70px;
+ background-color: whitesmoke;
+ z-index: 200;
+}
+
+.navbar .toggle-btn {
+ position: absolute;
+ right: 20px;
+ top: 30px;
+ height: 70px;
+ width: 70px;
+ transition: all 300ms ease-in-out 200ms;
+}
+
+.navbar .toggle-btn-home {
+ right: -200px;
+}
+
+.navbar .header {
+ position: absolute;
+ top: 50%;
+ top: calc(9px + 50%);
+ right: 50%;
+ transform: translate(50%, -50%);
+ font-size: 40;
+ font-weight: 700;
+ text-align: center;
+ user-select: none;
+ text-transform: uppercase;
+ font-family: Arial, Helvetica, sans-serif;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: ltr;
+ width: 600px;
+}
+
+.navbar .toggle-btn span {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 70%;
+ height: 4px;
+ background: black;
+ transition: all 200ms ease;
+}
+
+.navbar .toggle-btn span:nth-child(1) {
+ transition: top 200ms ease-in-out;
+ top: 30%;
+}
+
+.navbar .toggle-btn span:nth-child(3) {
+ transition: top 200ms ease-in-out;
+ top: 70%;
+}
+
+.navbar .toggle-btn.active {
+ transition: transform 200ms ease-in-out 200ms;
+ transform: rotate(135deg);
+}
+
+.navbar .toggle-btn.active span:nth-child(1) {
+ top: 50%;
+}
+
+.navbar .toggle-btn.active span:nth-child(2) {
+ transform: translate(-50%, -50%) rotate(90deg);
+}
+
+.navbar .toggle-btn.active span:nth-child(3) {
+ top: 50%;
+}
+
+.sidebar {
+ position: fixed;
+ top: 120px;
+ opacity: 0;
+ right: -100%;
+ width: 100%;
+ height: calc(100% - (120px));
+ z-index: 101;
+ background-color: whitesmoke;
+ transition: all 400ms ease 50ms;
+ padding: 20px;
+ // overflow-y: auto;
+ // -webkit-overflow-scrolling: touch;
+ // border-right: 5px solid black;
+}
+
+.sidebar .item {
+ width: 100%;
+ padding: 13px 12px;
+ border-bottom: 1px solid rgba(200, 200, 200, 0.7);
+ font-family: Arial, Helvetica, sans-serif;
+ font-style: normal;
+ font-weight: normal;
+ user-select: none;
+ display: inline-flex;
+ font-size: 35px;
+ text-transform: uppercase;
+ color: black;
+
+}
+
+.sidebar .ink:focus {
+ outline: 1px solid blue;
+}
+
+.sidebarButtons {
+ top: 80px;
+ position: relative;
+}
+
+.home {
+ position: absolute;
+ top: 30px;
+ left: 30px;
+ font-size: 60;
+ user-select: none;
+ text-transform: uppercase;
+ font-family: Arial, Helvetica, sans-serif;
+ z-index: 200;
+}
+
+.item-type {
+ display: inline;
+ text-transform: lowercase;
+ margin-left: 20px;
+ font-size: 35px;
+ font-style: italic;
+ color: rgb(28, 28, 28);
+}
+
+.item-title {
+ max-width: 70%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.right {
+ margin-left: 20px;
+ z-index: 200;
+}
+
+.open {
+ right: 20px;
+ position: absolute;
+}
+
+.left {
+ width: 100%;
+ height: 100%;
+}
+
+.sidebar .logout {
+ width: 100%;
+ padding: 13px 12px;
+ border-bottom: 1px solid rgba(200, 200, 200, 0.7);
+ font-family: Arial, Helvetica, sans-serif;
+ font-style: normal;
+ font-weight: normal;
+ user-select: none;
+ font-size: 30px;
+ text-transform: uppercase;
+ color: black;
+}
+
+.sidebar .settings {
+ width: 100%;
+ padding: 13px 12px;
+ border-bottom: 1px solid rgba(200, 200, 200, 0.7);
+ font-family: Arial, Helvetica, sans-serif;
+ font-style: normal;
+ font-weight: normal;
+ user-select: none;
+ font-size: 30px;
+ text-transform: uppercase;
+ color: black;
+}
+
+
+.sidebar.active {
+ position: absolute;
+ right: 0%;
+ opacity: 1;
+ z-index: 101;
+}
+
+.back {
+ position: absolute;
+ left: 42px;
+ top: 0;
+ background: #1a1a1a;
+ width: 50px;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ flex-direction: column;
+ align-items: center;
+ border-radius: 10px;
+ font-size: 25px;
+ user-select: none;
+ z-index: 100;
+}
+
+.pathbar {
+ position: fixed;
+ top: 118px;
+ left: 0px;
+ background: #1a1a1a;
+ z-index: 120;
+ border-radius: 0px;
+ width: 100%;
+ height: 80px;
+ overflow: hidden;
+}
+
+.pathname {
+ position: relative;
+ font-size: 25;
+ top: 50%;
+ width: 86%;
+ left: 12%;
+ color: whitesmoke;
+ transform: translate(0%, -50%);
+ z-index: 20;
+ font-family: sans-serif;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ direction: rtl;
+ text-align: left;
+ text-transform: uppercase;
+}
+
+.docButton {
+ position: relative;
+ width: 100px;
+ display: flex;
+ height: 100px;
+ font-size: 70px;
+ text-align: center;
+ border: 3px solid black;
+ margin: 20px;
+ z-index: 100;
+ border-radius: 100%;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+}
+
+.docButtonContainer {
+ top: 80%;
+ position: absolute;
+ display: flex;
+ transform: translate(-50%, 0);
+ left: 50%;
+ z-index: 100;
+}
+
+.scrollmenu {
+ overflow: auto;
+ width: 100%;
+ height: 100%;
+ white-space: nowrap;
+ display: inline-flex;
+}
+
+.pathbarItem {
+ position: relative;
+ display: flex;
+ align-items: center;
+ color: whitesmoke;
+ text-align: center;
+ justify-content: center;
+ user-select: none;
+ transform: translate(100px, 0px);
+ font-size: 30px;
+ padding: 10px;
+ text-transform: uppercase;
+}
+
+.pathbarText {
+ font-family: sans-serif;
+ text-align: center;
+ height: 50px;
+ padding: 10px;
+ font-size: 30px;
+ border-radius: 10px;
+ text-transform: uppercase;
+ margin-left: 20px;
+ position: relative;
+}
+
+
+.pathIcon {
+ transform: translate(0px, 0px);
+ position: relative;
+}
+
+.hidePath {
+ position: absolute;
+ height: 100%;
+ width: 200px;
+ left: 0px;
+ top: 0px;
+ background-image: linear-gradient(to right, #1a1a1a, rgba(0, 0, 0, 0));
+ text-align: center;
+ user-select: none;
+ z-index: 99;
+ pointer-events: none;
+}
+
+.toolbar {
+ left: 50%;
+ transform: translate(-50%);
+ position: absolute;
+ height: max-content;
+ top: 0px;
+ border-radius: 20px;
+ background-color: lightgrey;
+ opacity: 0;
+ transition: all 400ms ease 50ms;
+}
+
+.toolbar.active {
+ display: inline-block;
+ width: 300px;
+ padding: 5px;
+ opacity: 1;
+ height: max-content;
+ top: -450px;
+}
+
+.colorSelector {
+ position: absolute;
+ top: 550px;
+ left: 300px;
+ transform: translate(-50%, 0);
+ z-index: 100;
+ display: inline-flex;
+ width: max-content;
+ height: max-content;
+ pointer-events: all;
+ font-size: 90px;
+}
+
+.widthSelector {
+ display: inline-flex;
+ width: max-content;
+ height: 100px;
+ padding: 10px;
+}
+
+.slider {
+ -webkit-appearance: none;
+ /* Override default CSS styles */
+ appearance: none;
+ width: 100%;
+ /* Full-width */
+ height: 25px;
+ /* Specified height */
+ background: #d3d3d3;
+ /* Grey background */
+ outline: none;
+ /* Remove outline */
+}
+
+.colorButton {
+ width: 70px;
+ margin: 10px;
+ height: 70px;
+ border-style: solid;
+ border-width: 3px;
+ border-radius: 100%;
+}
+
+.homeSwitch {
+ position: fixed;
+ top: 212;
+ right: 36px;
+ display: inline-flex;
+ width: max-content;
+ z-index: 99;
+ height: 70px;
+}
+
+.list {
+ width: 70px;
+ height: 70px;
+ margin: 5;
+ padding: 10;
+ align-items: center;
+ text-align: center;
+ font-size: 50;
+ border-style: solid;
+ border-width: 3;
+ border-color: black;
+ background: whitesmoke;
+ align-self: center;
+ border-radius: 10px;
+}
+
+.list.active {
+ color: darkred;
+ border-color: darkred;
+} \ No newline at end of file