diff options
Diffstat (limited to 'src/client')
48 files changed, 1377 insertions, 462 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ef216001c..02c3e55ed 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -54,6 +54,26 @@ export class CurrentUserUtils { removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle" }); } + // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location) + if (doc["template-mobile-button"] === undefined) { + const queryTemplate = this.mobileButton({ + title: "NEW MOBILE BUTTON", + onClick: undefined, + _backgroundColor: "lightgrey" + }, + [this.ficon({ + ignoreClick: true, + icon: "mobile", + backgroundColor: "rgba(0,0,0,0)" + }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); + doc["template-mobile-button"] = CurrentUserUtils.ficon({ + onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, + removeDropProperties: new List<string>(["dropAction"]), title: "mobile button", icon: "mobile" + }); + } if (doc["template-button-slides"] === undefined) { const slideTemplate = Docs.Create.MultirowDocument( @@ -219,6 +239,7 @@ export class CurrentUserUtils { doc["template-button-slides"] as Doc, doc["template-button-description"] as Doc, doc["template-button-query"] as Doc, + doc["template-mobile-button"] as Doc, doc["template-button-detail"] as Doc, doc["template-button-link"] as Doc, doc["template-button-switch"] as Doc]; @@ -343,7 +364,7 @@ export class CurrentUserUtils { } else { const templateIconsDoc = Cast(doc["template-icons"], Doc, null); const requiredTypes = [doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc]; + doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc]; DocListCastAsync(templateIconsDoc.data).then(async curIcons => { await Promise.all(curIcons!); requiredTypes.map(ntype => Doc.AddDocToList(templateIconsDoc, "data", ntype)); @@ -368,7 +389,7 @@ export class CurrentUserUtils { doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300 }); } if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" }) + doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script" }); } if (doc.emptyDocHolder === undefined) { doc.emptyDocHolder = Docs.Create.DocumentDocument( @@ -378,6 +399,9 @@ export class CurrentUserUtils { if (doc.emptyWebpage === undefined) { doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 600, UseCors: true }); } + if (doc.activeMobileMenu === undefined) { + this.setupActiveMobileMenu(doc); + } return [ { title: "Drag a comparison box", label: "Comp", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc }, { title: "Drag a collection", label: "Col", icon: "folder", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc }, @@ -391,7 +415,7 @@ export class CurrentUserUtils { { title: "Drag a search box", label: "Query", icon: "search", ignoreClick: true, drag: 'Docs.Create.QueryDocument({ _width: 200, title: "an image of a cat" })' }, { title: "Drag a scripting box", label: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc }, { title: "Drag an import folder", label: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, - { title: "Drag a mobile view", label: "Phone", icon: "phone", ignoreClick: true, drag: 'Doc.UserDoc().activeMobile' }, + { title: "Drag a mobile view", label: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, { title: "Drag an instance of the device collection", label: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, @@ -445,25 +469,72 @@ export class CurrentUserUtils { return doc.myItemCreators as Doc; } - static setupMobileButtons(doc: Doc, buttons?: string[]) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "record", icon: "microphone", ignoreClick: true, click: "FILL" }, - { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc }, - { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc }, - // { title: "draw", icon: "pen-nib", click: 'switchMobileView(setupMobileInkingDoc, renderMobileInking, onSwitchMobileInking);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "red", activeInkPen: doc }, - { title: "upload", icon: "upload", click: 'switchMobileView(setupMobileUploadDoc, renderMobileUpload, onSwitchMobileUpload);', backgroundColor: "orange" }, - // { title: "upload", icon: "upload", click: 'uploadImageMobile();', backgroundColor: "cyan" }, - ]; - return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, - ischecked: data.ischecked ? ComputedField.MakeFunction(data.ischecked) : undefined, activeInkPen: data.activeInkPen, - backgroundColor: data.backgroundColor, removeDropProperties: new List<string>(["dropAction"]), dragFactory: data.dragFactory, + // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu + static setupActiveMobileMenu(doc: Doc) { + if (doc.activeMobileMenu === undefined) { + console.log("undefined"); + doc.activeMobileMenu = this.setupMobileMenu(); + } + return doc.activeMobileMenu as Doc; + } + + // Sets up mobileMenu stacking document + static setupMobileMenu() { + const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { + _width: 980, ignoreClick: true, lockedPosition: false, _chromeStatus: "disabled", title: "home", _yMargin: 100 })); + return menu; + } + + // SEts up mobile buttons for inside mobile menu + static setupMobileButtons(doc?: Doc, buttons?: string[]) { + const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ + { title: "WORKSPACES", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " }, + { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, + { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." }, + { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." }, + { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." }, + { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." } + ]; + // returns a list of mobile buttons + return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data => + this.mobileButton({ + title: data.title, + lockedPosition: true, + onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, + _backgroundColor: data.backgroundColor + }, + [this.ficon({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)" }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])]) + ); } + // sets up the main document for the mobile button + static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, { + ...opts, + dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15, + borderRounding: "5px", boxShadow: "0 0", _chromeStatus: "disabled" + }) as any as Doc + + // sets up the text container for the information contained within the mobile button + static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, { + ...opts, + dropAction: undefined, removeDropProperties: new List<string>(["dropAction"]), _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25, + backgroundColor: "rgba(0,0,0,0)", borderRounding: "0", boxShadow: "0 0", _chromeStatus: "disabled", ignoreClick: true + }) as any as Doc + + // Sets up the title of the button + static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, { + ...opts, + dropAction: undefined, title: buttonTitle, _fontSize: 37, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)" + }) as any as Doc + + // Sets up the description of the button + static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, { + ...opts, + dropAction: undefined, title: "info", _fontSize: 25, _xMargin: 0, _yMargin: 0, ignoreClick: true, _chromeStatus: "disabled", backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, + }) as any as Doc + + static setupThumbButtons(doc: Doc) { const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, ischecked?: string, clipboard?: Doc, activeInkPen?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, @@ -497,31 +568,12 @@ export class CurrentUserUtils { return Cast(userDoc.thumbDoc, Doc); } - static setupMobileDoc(userDoc: Doc) { - return userDoc.activeMoble ?? Docs.Create.MasonryDocument(CurrentUserUtils.setupMobileButtons(userDoc), { - _columnWidth: 100, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", title: "buttons", _autoHeight: true, _yMargin: 5 - }); - } - - static setupMobileInkingDoc(userDoc: Doc) { - return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white" }); - } - - static setupMobileUploadDoc(userDoc: Doc) { - // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" }) - const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", { - title: "Upload Images From the Web", _chromeStatus: "enabled", lockedPosition: true - }); - const uploadDoc = Docs.Create.StackingDocument([], { - title: "Mobile Upload Collection", backgroundColor: "white", lockedPosition: true - }); - return Docs.Create.StackingDocument([webDoc, uploadDoc], { - _width: screen.width, lockedPosition: true, _chromeStatus: "disabled", title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray" - }); + static setupLibrary(userDoc: Doc) { + return CurrentUserUtils.setupWorkspaces(userDoc); } - // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. - // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) + // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. + // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) static async setupToolsBtnPanel(doc: Doc, sidebarContainer: Doc) { // setup a masonry view of all he creators const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); @@ -625,7 +677,7 @@ export class CurrentUserUtils { return doc["tabs-button-library"] as Doc; } - // setup the Search button which will display the search panel. + // setup the Search button which will display the search panel. static setupSearchBtnPanel(doc: Doc, sidebarContainer: Doc) { if (doc["tabs-button-search"] === undefined) { doc["tabs-button-search"] = new PrefetchProxy(Docs.Create.ButtonDocument({ @@ -715,12 +767,14 @@ export class CurrentUserUtils { } } + // Right sidebar is where mobile uploads are contained static setupRightSidebar(doc: Doc) { if (doc.rightSidebarCollection === undefined) { - doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Right Sidebar" })); + doc.rightSidebarCollection = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Mobile Uploads" })); } } + static setupClickEditorTemplates(doc: Doc) { if (doc["clickFuncs-child"] === undefined) { const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( @@ -771,13 +825,14 @@ export class CurrentUserUtils { doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "none"); doc.activeDash = StrCast(doc.activeDash, "0"); doc.fontSize = NumCast(doc.fontSize, 12); - doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // - doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // + doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // + doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates this.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing - this.setupOverlays(doc); // documents in overlay layer + this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile + this.setupOverlays(doc); // documents in overlay layer this.setupDockedButtons(doc); // the bottom bar of font icons this.setupDefaultPresentation(doc); // presentation that's initially triggered await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels @@ -816,9 +871,5 @@ export class CurrentUserUtils { } } -Scripting.addGlobal(function setupMobileInkingDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileInkingDoc(userDoc); }, - "initializes the Mobile inking document", "(userDoc: Doc)"); -Scripting.addGlobal(function setupMobileUploadDoc(userDoc: Doc) { return CurrentUserUtils.setupMobileUploadDoc(userDoc); }, - "initializes the Mobile upload document", "(userDoc: Doc)"); Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); }, - "creates a new workspace when called");
\ No newline at end of file + "creates a new workspace when called"); diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index edeb461e0..02b444cd3 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -27,7 +27,7 @@ export namespace InteractionUtils { export interface MultiTouchEventDisposer { (): void; } /** - * + * * @param element - element to turn into a touch target * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart) */ @@ -278,8 +278,8 @@ export namespace InteractionUtils { /** * Returns euclidean distance between two points - * @param pt1 - * @param pt2 + * @param pt1 + * @param pt2 */ export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); @@ -410,4 +410,4 @@ export namespace InteractionUtils { // } // } } -}
\ No newline at end of file +} diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 9b4dc2630..749fabfcc 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -49,7 +49,7 @@ export class LinkManager { } public deleteLink(linkDoc: Doc): boolean { - if (LinkManager.Instance.LinkManagerDoc) { + if (LinkManager.Instance.LinkManagerDoc && linkDoc instanceof Doc) { Doc.RemoveDocFromList(LinkManager.Instance.LinkManagerDoc, "data", linkDoc); return true; } diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index fa2609ca2..13c65042c 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -134,4 +134,25 @@ } +} + +@media only screen and (max-device-width: 480px) { + .settings-interface { + width: 80vw; + height: 400px; + } + + .settings-interface .settings-body .settings-content input { + font-size: 30; + } + + .settings-interface button { + width: 100%; + font-size: 30px; + background: #b2cef8; + } + + .settings-interface .settings-heading { + font-size: 25; + } }
\ No newline at end of file diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss index e56574bb7..be21cec12 100644 --- a/src/client/views/AntimodeMenu.scss +++ b/src/client/views/AntimodeMenu.scss @@ -42,4 +42,35 @@ background-repeat: no-repeat; background-position: left center; } +} + +@media only screen and (max-device-width: 480px) { + .antimodeMenu-cont { + height: 100px; + width: 100vw; + + &.with-rows { + flex-direction: column-reverse; + } + + .antimodeMenu-row { + display: flex; + height: 100%; + width: 100%; + } + + .antimodeMenu-button { + background-color: transparent; + width: 100px; + height: 100px; + + &.active { + background-color: #121212; + } + } + + .antimodeMenu-button:hover { + background-color: #121212; + } + } }
\ No newline at end of file diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index 3e4d20fea..cb293dee4 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -51,18 +51,15 @@ export default abstract class AntimodeMenu extends React.Component { if (this._opacity === 0.2) { this._transitionProperty = "opacity"; this._transitionDuration = "0.1s"; - this._transitionDelay = ""; - this._opacity = 0; - this._left = this._top = -300; } if (forceOut) { this._transitionProperty = ""; this._transitionDuration = ""; - this._transitionDelay = ""; - this._opacity = 0; - this._left = this._top = -300; } + this._transitionDelay = ""; + this._opacity = 0; + this._left = this._top = -300; } } @@ -131,7 +128,7 @@ export default abstract class AntimodeMenu extends React.Component { } protected getDragger = () => { - return <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />; + return <div className="antimodeMenu-dragger" key="dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} />; } protected getElement(buttons: JSX.Element[]) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c05ca33fb..40a9d0c45 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -293,4 +293,4 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV </div> </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss index 107077792..f61f4a05e 100644 --- a/src/client/views/GestureOverlay.scss +++ b/src/client/views/GestureOverlay.scss @@ -1,7 +1,7 @@ .gestureOverlay-cont { width: 100vw; height: 100vh; - position: absolute; + position: fixed; top: 0; left: 0; touch-action: none; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 85695bbac..487467b2b 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -5,7 +5,6 @@ import { Doc } from "../../fields/Doc"; import { InkData, InkTool } from "../../fields/InkField"; import { Cast, FieldValue, NumCast } from "../../fields/Types"; import MobileInkOverlay from "../../mobile/MobileInkOverlay"; -import MobileInterface from "../../mobile/MobileInterface"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; import { MobileInkOverlayContent } from "../../server/Message"; import { emptyFunction, emptyPath, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, returnEmptyFilter } from "../../Utils"; @@ -112,7 +111,7 @@ export default class GestureOverlay extends Touchable { onReactTouchStart = (te: React.TouchEvent) => { document.removeEventListener("touchmove", this.onReactHoldTouchMove); document.removeEventListener("touchend", this.onReactHoldTouchEnd); - if (RadialMenu.Instance._display === true) { + if (RadialMenu.Instance?._display === true) { te.preventDefault(); te.stopPropagation(); RadialMenu.Instance.closeMenu(); @@ -162,8 +161,8 @@ export default class GestureOverlay extends Touchable { // -- radial menu code -- this._holdTimer = setTimeout(() => { console.log("hold"); - const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); - const pt: any = te.touches[te.touches.length - 1]; + const target = document.elementFromPoint(te.changedTouches?.item(0).clientX, te.changedTouches?.item(0).clientY); + const pt: any = te.touches[te.touches?.length - 1]; if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) { target?.dispatchEvent( new CustomEvent<InteractionUtils.MultiTouchEvent<React.TouchEvent>>("dashOnTouchHoldStart", @@ -580,14 +579,6 @@ export default class GestureOverlay extends Touchable { const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); //push first points to so interactionUtil knows pointer is up this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); - if (MobileInterface.Instance && MobileInterface.Instance.drawingInk) { - DocServer.Mobile.dispatchGesturePoints({ - points: this._points, - bounds: B, - color: ActiveInkColor(), - width: ActiveInkWidth() - }); - } const initialPoint = this._points[0.]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); @@ -725,7 +716,7 @@ export default class GestureOverlay extends Touchable { this._points = []; switch (shape) { //must push an extra point in the end so InteractionUtils knows pointer is up. - //must be (points[0].X,points[0]-1) + //must be (points[0].X,points[0]-1) case "rectangle": this._points.push({ X: left, Y: top }); this._points.push({ X: right, Y: top }); @@ -912,7 +903,7 @@ export default class GestureOverlay extends Touchable { } } -// export class +// export class export enum ToolglassTools { InkToText = "inktotext", @@ -949,4 +940,4 @@ Scripting.addGlobal(function resetPen() { }, "resets the pen tool"); Scripting.addGlobal(function createText(text: any, x: any, y: any) { GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text); -}, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)");
\ No newline at end of file +}, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)"); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index c85849adb..ff8380965 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -335,4 +335,4 @@ export default class KeyManager { }; }); -}
\ No newline at end of file +} diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index c32e76cec..7d6e7e5dd 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -72,8 +72,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume overflow: "visible", }} onContextMenu={() => { - ContextMenu.Instance.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" }); - ContextMenu.Instance.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" }); + ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" }); + ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" }); }} ><defs> </defs> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f6db1af66..fab1798e2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -99,7 +99,7 @@ export class MainView extends React.Component { window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("paste", KeyManager.Instance.paste as any); - document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on + document.addEventListener("dash", (e: any) => { // event used by chrome plugin to tell Dash which document to focus on const id = FormattedTextBox.GetDocFromUrl(e.detail); DocServer.GetRefField(id).then(doc => { if (doc instanceof Doc) { diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f65a89422..bd0e4fc9a 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -87,7 +87,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped()) { - ContextMenu.Instance.addItem({ + ContextMenu.Instance?.addItem({ description: "Make Hero Image", event: () => { const index = NumCast(this.layoutDoc._itemIndex); (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 23e8a4d49..3c42a2f1c 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -581,14 +581,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { onZoomMenu = (e: React.WheelEvent) => { this.props.active(true) && e.stopPropagation(); if (this.menuCoordinates[0] > e.screenX) { - this.menuCoordinates[0] -= e.screenX //* this.scale; + this.menuCoordinates[0] -= e.screenX; //* this.scale; } else { - this.menuCoordinates[0] += e.screenX //* this.scale; + this.menuCoordinates[0] += e.screenX; //* this.scale; } if (this.menuCoordinates[1] > e.screenY) { - this.menuCoordinates[1] -= e.screenY //* this.scale; + this.menuCoordinates[1] -= e.screenY; //* this.scale; } else { - this.menuCoordinates[1] += e.screenY //* this.scale; + this.menuCoordinates[1] += e.screenY; //* this.scale; } } diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 3d8ec2fd5..8fc74a9c6 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -33,8 +33,9 @@ .collectionStackingViewFieldColumn { height: max-content; } + .collectionStackingViewFieldColumnDragging { - height:100%; + height: 100%; } .collectionSchemaView-previewDoc { @@ -425,4 +426,15 @@ .rc-switch-checked .rc-switch-inner { left: 8px; } +} + +@media only screen and (max-device-width: 480px) { + + .collectionStackingView .collectionStackingView-columnDragger, + .collectionMasonryView .collectionStackingView-columnDragger { + width: 0.1; + height: 0.1; + opacity: 0; + font-size: 0; + } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 7bdf5e7df..9d8790dda 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -498,4 +498,4 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) </div> </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index ecd16d22f..ed8535ecb 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -9,7 +9,6 @@ import { ScriptField } from "../../../fields/ScriptField"; import { WebField } from "../../../fields/URLField"; import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types"; import { GestureUtils } from "../../../pen-gestures/GestureUtils"; -import { Upload } from "../../../server/SharedMediaTypes"; import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Networking } from "../../Network"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 705fbdd34..10ebe571b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -180,7 +180,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus // this is called with the document that was dragged and the collection to move it into. // if the target collection is the same as this collection, then the move will be allowed. // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. + // moving it into the target. @action.bound moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { @@ -202,7 +202,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus showIsTagged = () => { return (null); - // this section would display an icon in the bototm right of a collection to indicate that all + // this section would display an icon in the bototm right of a collection to indicate that all // photos had been processed through Google's content analysis API and Google's tags had been // assigned to the documents googlePhotosTags field. // const children = DocListCast(this.props.Document[this.props.fieldKey]); @@ -277,7 +277,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus } onContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + const cm = ContextMenu.Instance; + if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 this.setupViewTypes("Add a Perspective...", vtype => { const newRendition = Doc.MakeAlias(this.props.Document); newRendition._viewType = vtype; @@ -285,7 +286,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return newRendition; }, false); - const existing = ContextMenu.Instance.findByDescription("Options..."); + const existing = cm.findByDescription("Options..."); const layoutItems = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); if (this.props.Document.childLayout instanceof Doc) { @@ -296,9 +297,9 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus } layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); - !existing && ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" }); + !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" }); - const existingOnClick = ContextMenu.Instance.findByDescription("OnClick..."); + const existingOnClick = cm.findByDescription("OnClick..."); const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; const funcs = [ { key: "onChildClick", name: "On Child Clicked" }, @@ -314,13 +315,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus icon: "edit", event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), })); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" }); if (!Doc.UserDoc().noviceMode) { - const more = ContextMenu.Instance.findByDescription("More..."); + const more = cm.findByDescription("More..."); const moreItems = more && "subitems" in more ? more.subitems : []; moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); - !more && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); + !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); } } } @@ -571,5 +572,3 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus </div>); } } - - diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 822e15aed..b1e8d20ad 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -81,6 +81,12 @@ // margin-top: 10px; } + @media only screen and (max-device-width: 480px) { + .collectionViewBaseChrome-collapse { + display: none; + } + } + .collectionViewBaseChrome-template, .collectionViewBaseChrome-viewModes { display: grid; diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index ab0df88f8..c20f2162a 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -316,7 +316,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro render() { const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled"; - const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform().Scale); + const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale); return ( <div className="collectionViewChrome-cont" style={{ top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined, @@ -452,9 +452,9 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView @action toggleSort = () => { this.props.CollectionView.props.Document._columnsSort = - this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" : - this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending"; - }; + this.props.CollectionView.props.Document._columnsSort === "descending" ? "ascending" : + this.props.CollectionView.props.Document._columnsSort === "ascending" ? undefined : "descending"; + } @action resetValue = () => { this._currentKey = this.pivotField; }; render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 546a4307c..21b0473c9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -602,7 +602,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { // bcz: theres should be a better way of doing these than referencing these static instances directly MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2 - PDFMenu.Instance.fadeOut(true); const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true); @@ -870,7 +869,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => { const state = HistoryUtil.getState(); - // TODO This technically isn't correct if type !== "doc", as + // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state if (state.type === "doc" && this.Document._panX !== undefined && this.Document._panY !== undefined) { const init = state.initializers![this.Document[Id]]; @@ -1204,7 +1203,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P private thumbIdentifier?: number; onContextMenu = (e: React.MouseEvent) => { - if (this.props.annotationsKey) return; + if (this.props.annotationsKey || !ContextMenu.Instance) return; const appearance = ContextMenu.Instance.findByDescription("Appearance..."); const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; @@ -1453,4 +1452,4 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF {this.props.children()} </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss index a9fab4c1e..753de6bef 100644 --- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss @@ -9,7 +9,6 @@ height: 100%; } - } .sketch-picker { @@ -36,6 +35,44 @@ background: #323232; display: block; + } +} + +@media only screen and (max-device-width: 480px) { + .antimodeMenu-button { + font-size: 50%; + + .color-preview { + width: 100%; + height: 100%; + } + + } + + .sketch-picker { + background: #323232; + + .flexbox-fit { + background: #323232; + } + } + + .btn-group { + display: grid; + grid-template-columns: auto auto; + /* Make the buttons appear below each other */ + } + + .btn2-group { + display: block; + background: #323232; + grid-template-columns: auto; + /* Make the buttons appear below each other */ + .antimodeMenu-button { + background: #323232; + display: block; + font-size: 50%; + } } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx index f1032adaa..f47fca6ac 100644 --- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx @@ -20,6 +20,8 @@ import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscr library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve); + + @observer export default class InkOptionsMenu extends AntimodeMenu { static Instance: InkOptionsMenu; @@ -42,6 +44,8 @@ export default class InkOptionsMenu extends AntimodeMenu { @observable _dashBtn = false; @observable _shapeBtn = false; + + constructor(props: Readonly<{}>) { super(props); InkOptionsMenu.Instance = this; @@ -325,4 +329,4 @@ Scripting.addGlobal(function activatePen(penBtn: any) { Doc.SetSelectedTool(InkTool.None); InkOptionsMenu.Instance.fadeOut(true); } -});
\ No newline at end of file +}); diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 53b54d7e4..c959b79f5 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,141 +1,169 @@ -.audiobox-container, .audiobox-container-interactive { +.audiobox-container, +.audiobox-container-interactive { width: 100%; height: 100%; position: inherit; - display:flex; + display: flex; pointer-events: all; - cursor:default; + cursor: default; + .audiobox-buttons { display: flex; width: 100%; align-items: center; } + .audiobox-handle { - width:20px; - height:100%; - display:inline-block; + width: 20px; + height: 100%; + display: inline-block; } - .audiobox-control, .audiobox-control-interactive { - top:0; + + .audiobox-control, + .audiobox-control-interactive { + top: 0; max-height: 32px; width: 100%; - display:inline-block; + display: inline-block; pointer-events: none; } + .audiobox-control-interactive { pointer-events: all; } + .audiobox-record { pointer-events: all; - width:100%; - height:100%; + width: 100%; + height: 100%; position: relative; pointer-events: none; } + .audiobox-record-interactive { pointer-events: all; - width:100%; - height:100%; + width: 100%; + height: 100%; position: relative; } + .audiobox-controls { - width:100%; - height:100%; + width: 100%; + height: 100%; position: relative; display: flex; padding-left: 2px; + .audiobox-player { - margin-top:auto; - margin-bottom:auto; - width:100%; + margin-top: auto; + margin-bottom: auto; + width: 100%; height: 80%; position: relative; padding-right: 5px; display: flex; - .audiobox-playhead, .audiobox-dictation { + + .audiobox-playhead, + .audiobox-dictation { position: relative; margin-top: auto; margin-bottom: auto; width: 25px; padding: 2px; } + .audiobox-dictation { align-items: center; display: inherit; background: dimgray; } + .audiobox-timeline { - position:relative; - height:100%; - width:100%; + position: relative; + height: 100%; + width: 100%; background: white; border: gray solid 1px; border-radius: 3px; + .audiobox-current { width: 1px; - height:100%; + height: 100%; background-color: red; position: absolute; } - .audiobox-linker, .audiobox-linker-mini { - position:absolute; - width:15px; - min-height:10px; - height:15px; - margin-left:-2.55px; - background:gray; + + .audiobox-linker, + .audiobox-linker-mini { + position: absolute; + width: 15px; + min-height: 10px; + height: 15px; + margin-left: -2.55px; + background: gray; border-radius: 100%; - opacity:0.9; + opacity: 0.9; background-color: transparent; box-shadow: black 2px 2px 1px; + .linkAnchorBox-cont { position: relative !important; - height: 100% !important; + height: 100% !important; width: 100% !important; - left:unset !important; - top:unset !important; + left: unset !important; + top: unset !important; } } + .audiobox-linker-mini { - width:8px; - min-height:8px; - height:8px; + width: 8px; + min-height: 8px; + height: 8px; box-shadow: black 1px 1px 1px; margin-left: -1; margin-top: -2; + .linkAnchorBox-cont { position: relative !important; - height: 100% !important; + height: 100% !important; width: 100% !important; - left:unset !important; - top:unset !important; + left: unset !important; + top: unset !important; } } - .audiobox-linker:hover, .audiobox-linker-mini:hover { - opacity:1; + + .audiobox-linker:hover, + .audiobox-linker-mini:hover { + opacity: 1; } - .audiobox-marker-container, .audiobox-marker-minicontainer { - position:absolute; - width:10px; - height:90%; - top:2.5%; - background:gray; + + .audiobox-marker-container, + .audiobox-marker-minicontainer { + position: absolute; + width: 10px; + height: 90%; + top: 2.5%; + background: gray; border-radius: 5px; box-shadow: black 2px 2px 1px; + .audiobox-marker { - position:relative; + position: relative; height: calc(100% - 15px); margin-top: 15px; } + .audio-marker:hover { border: orange 2px solid; } } + .audiobox-marker-minicontainer { - width:5px; + width: 5px; border-radius: 1px; + .audiobox-marker { - position:relative; + position: relative; height: calc(100% - 8px); margin-top: 8px; } @@ -143,4 +171,27 @@ } } } +} + + +@media only screen and (max-device-width: 480px) { + .audiobox-dictation { + font-size: 5em; + display: flex; + width: 100; + justify-content: center; + flex-direction: column; + align-items: center; + } + + .audiobox-container .audiobox-record, + .audiobox-container-interactive .audiobox-record { + font-size: 3em; + } + + .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead, + .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation, + .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead { + width: 70px; + } }
\ No newline at end of file diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 1a935d9b0..d5288fff6 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -161,7 +161,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument const funcs: ContextMenuProps[] = []; funcs.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when document selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } stopRecording = action(() => { @@ -193,7 +193,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument const newDoc = Docs.Create.TextDocument("", { title: "", _chromeStatus: "disabled", x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y) + NumCast(this.props.Document._height) + 10, - _width: NumCast(this.props.Document._width), _height: 3 * NumCast(this.props.Document._height) + _width: NumCast(this.props.Document._width), _height: 2 * NumCast(this.props.Document._height) }); Doc.GetProto(newDoc).recordingSource = this.dataDoc; Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`); @@ -228,7 +228,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument {!this.path ? <div className="audiobox-buttons"> <div className="audiobox-dictation" onClick={this.onFile}> - <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> + <FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> </div> <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "red" : "black" }}> {this.audioState === "recording" ? "STOP" : "RECORD"} diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 137b387c0..b3a9b6fee 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -71,4 +71,4 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument </div> </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 4f4f12521..3940b7a98 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -101,4 +101,4 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp render() { return this.linkButton; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 21b6d8310..37d8df47d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -29,7 +29,6 @@ import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from '../collections/CollectionView'; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; @@ -42,6 +41,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); import { DocumentLinksButton } from './DocumentLinksButton'; +import { MobileInterface } from '../../../mobile/MobileInterface'; library.add(fa.faEdit, fa.faTrash, fa.faShare, fa.faDownload, fa.faExpandArrowsAlt, fa.faCompressArrowsAlt, fa.faLayerGroup, fa.faExternalLinkAlt, fa.faAlignCenter, fa.faCaretSquareRight, fa.faSquare, fa.faConciergeBell, fa.faWindowRestore, fa.faFolder, fa.faMapPin, fa.faLink, fa.faFingerprint, fa.faCrosshairs, fa.faDesktop, fa.faUnlock, fa.faLock, fa.faLaptopCode, fa.faMale, @@ -181,10 +181,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu const pt = me.touchEvent.touches[me.touchEvent.touches.length - 1]; RadialMenu.Instance.openMenu(pt.pageX - 15, pt.pageY - 15); - RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Delete this document", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "layer-group", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); - RadialMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "folder", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "onRight"), icon: "map-pin", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Delete", event: () => { this.props.ContainingCollectionView?.removeDocument(this.props.Document), RadialMenu.Instance.closeMenu(); }, icon: "external-link-square-alt", selected: -1 }); + // RadialMenu.Instance.addItem({ description: "Open in a new tab", event: () => this.props.addDocTab(this.props.Document, "onRight"), icon: "trash", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Pin", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin", selected: -1 }); + RadialMenu.Instance.addItem({ description: "Open", event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: "trash", selected: -1 }); SelectionManager.DeselectAll(); } @@ -345,12 +346,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // depending on the followLinkLocation property of the source (or the link itself as a fallback); followLinkClick = async (altKey: boolean, ctrlKey: boolean, shiftKey: boolean) => { const batch = UndoManager.StartBatch("follow link click"); - // open up target if it's not already in view ... + // open up target if it's not already in view ... const createViewFunc = (doc: Doc, followLoc: string, finished: Opt<() => void>) => { const targetFocusAfterDocFocus = () => { const where = StrCast(this.Document.followLinkLocation) || followLoc; const hackToCallFinishAfterFocus = () => { - finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout. + finished && setTimeout(finished, 0); // finished() needs to be called right after hackToCallFinishAfterFocus(), but there's no callback for that so we use the hacky timeout. return false; // we must return false here so that the zoom to the document is not reversed. If it weren't for needing to call finished(), we wouldn't need this function at all since not having it is equivalent to returning false }; this.props.addDocTab(doc, where) && this.props.focus(doc, BoolCast(this.Document.followLinkZoom, true), undefined, hackToCallFinishAfterFocus); // add the target and focus on it. @@ -724,17 +725,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu return; } e.persist(); - e?.stopPropagation(); + e.stopPropagation(); - if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 || - e.isDefaultPrevented()) { - e.preventDefault(); + if (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3 || + e?.isDefaultPrevented()) { + e?.preventDefault(); return; } e.preventDefault(); } const cm = ContextMenu.Instance; + if (!cm) return; const customScripts = Cast(this.props.Document.contextMenuScripts, listSpec(ScriptField), []); Cast(this.props.Document.contextMenuLabels, listSpec("string"), []).forEach((label, i) => @@ -875,7 +877,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu // DocumentViews should stop propagation of this event e.stopPropagation(); } - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); + cm.displayMenu(e.pageX - 15, e.pageY - 15); !SelectionManager.IsSelected(this, true) && SelectionManager.SelectDoc(this, false); }); const path = this.props.LibraryPath.reduce((p: string, d: Doc) => p + "/" + (Doc.AreProtosEqual(d, (Doc.UserDoc()["tabs-button-library"] as Doc).sourcePanel as Doc) ? "" : d.title), ""); @@ -943,7 +945,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu recommendations.documentIconHeight = 150; recommendations.sourceDoc = this.props.Document; recommendations.sourceDocContext = this.props.ContainingCollectionView!.props.Document; - CollectionDockingView.AddRightSplit(recommendations, undefined); + this.props.addDocTab(recommendations, "onRight"); // RecommendationsBox.Instance.displayRecommendations(e.pageX + 100, e.pageY); } @@ -975,13 +977,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu body.href = urls[i]; bodies.push(body); } - CollectionDockingView.AddRightSplit(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), undefined); + this.props.addDocTab(Docs.Create.SchemaDocument(headers, bodies, { title: `Showing External Recommendations for "${StrCast(doc.title)}"` }), "onRight"); this._showKPQuery = true; this._queries = kps.toString(); } // does Document set a layout prop - // does Document set a layout prop + // does Document set a layout prop setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)] && this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. getLayoutPropStr = (prop: string) => StrCast(this.setsLayoutProp(prop) ? this.props.Document[prop] : this.layoutDoc[prop]); @@ -1232,4 +1234,4 @@ Scripting.addGlobal(function toggleDetail(doc: any, layoutKey: string, otherKey: const dv = DocumentManager.Instance.getDocumentView(doc); if (dv?.props.Document.layoutKey === layoutKey) dv?.switchViews(otherKey !== "layout", otherKey.replace("layout_", "")); else dv?.switchViews(true, layoutKey.replace("layout_", "")); -});
\ No newline at end of file +}); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index c1c6f6baf..d16aa528c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -120,7 +120,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD }); const files = await res.json(); const url = Utils.prepend(files[0].path); - // upload to server with known URL + // upload to server with known URL const audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", _width: 200, _height: 32 }); audioDoc.treeViewExpandedView = "layout"; const audioAnnos = Cast(this.dataDoc[this.fieldKey + "-audioAnnotations"], listSpec(Doc)); @@ -174,14 +174,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD // }), icon: "expand-arrows-alt" // }); - const existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers..."); + const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); - !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); + !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); } } @@ -236,6 +236,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD if (this._curSuffix === "_m") this._mediumRetryCount++; if (this._curSuffix === "_l") this._largeRetryCount++; } + @action onError = (error: any) => { const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount; if (timeout < 5) { @@ -490,4 +491,4 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD </CollectionFreeFormView> </div >); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 92b443d3b..e8a08abb5 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -104,4 +104,4 @@ export class LinkDocPreview extends React.Component<Props> { {this.targetDocView} </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index c6a83b662..f2ab37984 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -5,24 +5,26 @@ height: 100%; width: 100%; overflow: hidden; - cursor:auto; + cursor: auto; transform-origin: top left; z-index: 0; + .pdfBox-ui { position: absolute; - width: 100%; - height: 100%; - z-index: 1; - pointer-events: none; + width: 100%; + height: 100%; + z-index: 1; + pointer-events: none; - .pdfBox-pageNums { + .pdfBox-pageNums { display: flex; flex-direction: row; height: 25px; position: absolute; left: 5px; top: 5px; - .pdfBox-overlayButton-fwd, + + .pdfBox-overlayButton-fwd, .pdfBox-overlayButton-back { background: #121721; height: 25px; @@ -34,29 +36,29 @@ border-radius: 3px; pointer-events: all; } - } - - .pdfBox-overlayButton { - border-bottom-left-radius: 50%; - display: flex; - justify-content: space-evenly; - align-items: center; - height: 20px; - background: none; - padding: 0; - position: absolute; - pointer-events: all; - - .pdfBox-overlayButton-arrow { - width: 0; - height: 0; - border-top: 10px solid transparent; - border-bottom: 10px solid transparent; - border-right: 15px solid #121721; - transition: all 0.5s; - } - - .pdfBox-overlayButton-iconCont { + } + + .pdfBox-overlayButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 20px; + background: none; + padding: 0; + position: absolute; + pointer-events: all; + + .pdfBox-overlayButton-arrow { + width: 0; + height: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-right: 15px solid #121721; + transition: all 0.5s; + } + + .pdfBox-overlayButton-iconCont { background: #121721; height: 20px; width: 25px; @@ -66,11 +68,11 @@ justify-content: center; border-radius: 3px; pointer-events: all; - } + } } - .pdfBox-nextIcon, - .pdfBox-prevIcon { + .pdfBox-nextIcon, + .pdfBox-prevIcon { background: #121721; height: 20px; width: 25px; @@ -81,96 +83,97 @@ border-radius: 3px; pointer-events: all; padding: 0px; - } - - .pdfBox-overlayButton:hover { - background: none; - } - - - .pdfBox-settingsCont { - position: absolute; - right: 0; - top: 3; - pointer-events: all; - - .pdfBox-settingsButton { - border-bottom-left-radius: 50%; - display: flex; - justify-content: space-evenly; - align-items: center; - height: 20px; - background: none; - padding: 0; - - .pdfBox-settingsButton-arrow { - width: 0; - height: 0; - border-top: 10px solid transparent; - border-bottom: 10px solid transparent; - border-right: 15px solid #121721; - transition: all 0.5s; - } - - .pdfBox-settingsButton-iconCont { - background: #121721; - height: 20px; - width: 25px; - display: flex; - justify-content: center; - align-items: center; - margin-left: -2px; - border-radius: 3px; - } - } - - .pdfBox-settingsButton:hover { - background: none; - } - - .pdfBox-settingsFlyout { - position: absolute; - background: #323232; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - right: 20px; - border-radius: 7px; - padding: 20px; - display: flex; - flex-direction: column; - font-size: 14px; - transition: all 0.5s; - - .pdfBox-settingsFlyout-title { - color: white; - } - - .pdfBox-settingsFlyout-kvpInput { - margin-top: 20px; - display: grid; - grid-template-columns: 47.5% 5% 47.5%; - } - } - } - - .pdfBox-overlayCont { - position: absolute; - width: calc(100% - 40px); - height: 20px; - background: #121721; - bottom: 0; - display: flex; - justify-content: center; - align-items: center; - overflow: hidden; - transition: left .5s; - pointer-events: all; - - .pdfBox-searchBar { - width: 70%; - font-size: 14px; - } - } + } + + .pdfBox-overlayButton:hover { + background: none; + } + + + .pdfBox-settingsCont { + position: absolute; + right: 0; + top: 3; + pointer-events: all; + + .pdfBox-settingsButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 20px; + background: none; + padding: 0; + + .pdfBox-settingsButton-arrow { + width: 0; + height: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + border-right: 15px solid #121721; + transition: all 0.5s; + } + + .pdfBox-settingsButton-iconCont { + background: #121721; + height: 20px; + width: 25px; + display: flex; + justify-content: center; + align-items: center; + margin-left: -2px; + border-radius: 3px; + } + } + + .pdfBox-settingsButton:hover { + background: none; + } + + .pdfBox-settingsFlyout { + position: absolute; + background: #323232; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + right: 20px; + border-radius: 7px; + padding: 20px; + display: flex; + flex-direction: column; + font-size: 14px; + transition: all 0.5s; + + .pdfBox-settingsFlyout-title { + color: white; + } + + .pdfBox-settingsFlyout-kvpInput { + margin-top: 20px; + display: grid; + grid-template-columns: 47.5% 5% 47.5%; + } + } + } + + .pdfBox-overlayCont { + position: absolute; + width: calc(100% - 40px); + height: 20px; + background: #121721; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + transition: left .5s; + pointer-events: all; + + .pdfBox-searchBar { + width: 70%; + font-size: 14px; + } + } } + .pdfBox-title-outer { width: 150%; height: 100%; @@ -179,9 +182,9 @@ z-index: 0; background: lightslategray; transform-origin: top left; - + .pdfBox-title { - color:lightgray; + color: lightgray; margin-top: auto; margin-bottom: auto; transform-origin: 42% 15%; @@ -217,4 +220,76 @@ } } } -}
\ No newline at end of file +} + +// CSS adjusted for mobile devices +@media only screen and (max-device-width: 480px) { + + .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton, + .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsButton { + height: 60px; + + .pdfBox-settingsButton-iconCont { + height: 60px; + width: 75px; + font-size: 30px; + } + + .pdfBox-settingsButton-arrow { + height: 60; + border-top: 30px solid transparent; + border-bottom: 30px solid transparent; + border-right: 30px solid #121721; + } + } + + + + .pdfBox .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout, + .pdfBox-interactive .pdfBox-ui .pdfBox-settingsCont .pdfBox-settingsFlyout { + font-size: 30px; + } + + + + .pdfBox .pdfBox-ui .pdfBox-overlayCont, + .pdfBox-interactive .pdfBox-ui .pdfBox-overlayCont { + height: 60px; + + .pdfBox-searchBar { + font-size: 40px; + } + } + + .pdfBox .pdfBox-ui .pdfBox-overlayButton, + .pdfBox-interactive .pdfBox-ui .pdfBox-overlayButton { + height: 60px; + + .pdfBox-overlayButton-iconCont { + height: 60px; + width: 75px; + font-size: 30; + } + + .pdfBox-overlayButton-arrow { + border-top: 30px solid transparent; + border-bottom: 30px solid transparent; + border-right: 30px solid #121721; + } + } + + button.pdfBox-search { + font-size: 30px; + width: 50px; + height: 50px; + } + + .pdfBox .pdfBox-ui .pdfBox-nextIcon, + .pdfBox .pdfBox-ui .pdfBox-prevIcon, + .pdfBox-interactive .pdfBox-ui .pdfBox-nextIcon, + .pdfBox-interactive .pdfBox-ui .pdfBox-prevIcon { + height: 50px; + width: 50px; + font-size: 30px; + } +} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index eb2a85eeb..1c5825a8f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -157,7 +157,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum <div className="pdfBox-overlayCont" key="cont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> <button className="pdfBox-overlayButton" title={searchTitle} /> <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} onKeyDown={e => e.keyCode === KeyCodes.ENTER && this.search(this._searchString, !e.shiftKey)} /> - <button title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}> + <button className="pdfBox-search" title="Search" onClick={e => this.search(this._searchString, !e.shiftKey)}> <FontAwesomeIcon icon="search" size="sm" color="white" /></button> <button className="pdfBox-prevIcon " title="Previous Annotation" onClick={this.prevAnnotation} > <FontAwesomeIcon style={{ color: "white" }} icon={"arrow-up"} size="lg" /> @@ -189,7 +189,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum <FontAwesomeIcon style={{ color: "white" }} icon="cog" size="lg" /> </div> </button> - <div className="pdfBox-settingsFlyout" style={{ right: `${this._flyout ? 20 : -600}px` }} > + <div className="pdfBox-settingsFlyout" style={{ right: `${this._flyout ? 20 : -1000}px` }} > <div className="pdfBox-settingsFlyout-title"> Annotation View Settings </div> @@ -229,7 +229,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum const classname = "pdfBox" + (this.active() ? "-interactive" : ""); return <div className={classname} style={{ width: !this.props.Document._fitWidth ? this.Document._nativeWidth || 0 : `${100 / this.contentScaling}%`, - height: !this.props.Document._fitWidth ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`, + //height adjusted for mobile (window.screen.width > 600) + height: !this.props.Document._fitWidth && (window.screen.width > 600) ? this.Document._nativeHeight || 0 : `${100 / this.contentScaling}%`, transform: `scale(${this.contentScaling})` }} > <div className="pdfBox-title-outer"> @@ -241,7 +242,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum isChildActive = (outsideReaction?: boolean) => this._isChildActive; @computed get renderPdfView() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}> + return <div className={"pdfBox"} onContextMenu={this.specificContextMenu} style={{ height: this.props.Document._scrollTop && !this.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.Document._height) * this.props.PanelWidth() / NumCast(this.Document._width) : undefined }}> <PDFViewer {...this.props} pdf={this._pdf!} url={pdfUrl!.url.pathname} active={this.props.active} loaded={this.loaded} setPdfViewer={this.setPdfViewer} ContainingCollectionView={this.props.ContainingCollectionView} renderDepth={this.props.renderDepth} PanelHeight={this.props.PanelHeight} PanelWidth={this.props.PanelWidth} diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index d48000e16..9f6af1bde 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -16,6 +16,7 @@ height: calc(100% - 25px); width: 100%; } + .presBox-buttons { width: 100%; background: gray; @@ -24,6 +25,7 @@ display: grid; grid-column-end: 4; grid-column-start: 1; + .presBox-viewPicker { height: 25; position: relative; @@ -31,10 +33,12 @@ grid-column: 1/2; min-width: 15px; } + select { background: #323232; color: white; } + .presBox-button { margin-right: 2.5%; margin-left: 2.5%; @@ -44,10 +48,12 @@ align-items: center; background: #323232; color: white; + svg { margin: auto; } } + .collectionViewBaseChrome-viewPicker { min-width: 50; width: 5%; @@ -56,17 +62,68 @@ display: inline-block; } } - .presBox-backward, .presBox-forward { + + .presBox-backward, + .presBox-forward { width: 25px; border-radius: 5px; - top:50%; + top: 50%; position: absolute; display: inline-block; } + .presBox-backward { - left:5; + left: 5; } + .presBox-forward { - right:5; + right: 5; + } +} + +// CSS adjusted for mobile devices +@media only screen and (max-device-width: 480px) { + .presBox-cont .presBox-buttons { + position: absolute; + top: 70%; + left: 50%; + transform: translate(-50%, 0); + width: max-content; + height: 15%; + z-index: 2; + align-items: center; + background: rgba(0, 0, 0, 0); + display: inline-flex; + + .presBox-button { + margin-top: 5%; + height: 250; + width: 300; + font-size: 100; + display: flex; + align-items: center; + background: #323232; + color: white; + } + + .presBox-viewPicker { + top: -70; + left: 2.5%; + height: 50; + width: 95%; + font-size: 30px; + position: absolute; + min-width: 50px; + } + } + + .presBox-cont .presBox-listCont { + top: 50; + height: calc(100% - 80px); + } + + .input, + .select { + font-size: 100%; } }
\ No newline at end of file diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx index ddfdb67b4..a3ac09a11 100644 --- a/src/client/views/nodes/RadialMenu.tsx +++ b/src/client/views/nodes/RadialMenu.tsx @@ -1,7 +1,6 @@ import React = require("react"); import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import MobileInterface from "../../../mobile/MobileInterface"; import "./RadialMenu.scss"; import { RadialMenuItem, RadialMenuProps } from "./RadialMenuItem"; @@ -26,7 +25,6 @@ export class RadialMenu extends React.Component { catchTouch = (te: React.TouchEvent) => { - console.log("caught"); te.stopPropagation(); te.preventDefault(); } @@ -38,7 +36,6 @@ export class RadialMenu extends React.Component { this._mouseY = e.clientY; this.used = false; document.addEventListener("pointermove", this.onPointerMove); - } @observable @@ -176,7 +173,6 @@ export class RadialMenu extends React.Component { @action openMenu = (x: number, y: number) => { - this._pageX = x; this._pageY = y; this._shouldDisplay; @@ -216,7 +212,7 @@ export class RadialMenu extends React.Component { render() { - if (!this._display || MobileInterface.Instance) { + if (!this._display) { return null; } const style = this._yRelativeToTop ? { left: this._pageX - 130, top: this._pageY - 130 } : diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 678494b27..0a094ba6a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -37,6 +37,7 @@ position: absolute; } } + .formattedTextBox-outer { position: relative; overflow: auto; @@ -72,7 +73,7 @@ .collectionfreeformview-container { position: relative; } - + >.formattedTextBox-sidebar-handle { right: unset; left: -5; @@ -95,7 +96,7 @@ .formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected, .formattedTextBox-inner, .formattedTextBox-inner-selected { height: 100%; - white-space: pre-wrap; + white-space: pre-wrap; .ProseMirror:hover { background: rgba(200,200,200,0.8); } @@ -262,19 +263,19 @@ footnote::after { border:unset; padding:0px; } - + .prosemirror-links a { float: left; color: white; text-decoration: none; border-radius: 3px; } - + .prosemirror-links a:hover { background-color: #eee; color: black; } - + .prosemirror-anchor:hover .prosemirror-links { display: grid; } @@ -321,7 +322,7 @@ footnote::after { .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 1.4em;} .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;} .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.4em;} - + .bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " } .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } @@ -331,7 +332,7 @@ footnote::after { .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; vertical-align: top; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; vertical-align: top; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; vertical-align: top; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } - + .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; vertical-align: top; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; vertical-align: top; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; vertical-align: top; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } @@ -346,4 +347,284 @@ footnote::after { .ProseMirror:hover { background: unset; } -}
\ No newline at end of file +} + +@media only screen and (max-width: 1000px) { + @import "../../globalCssVariables"; + + .ProseMirror { + width: 100%; + height: 100%; + min-height: 100%; + } + + .ProseMirror:focus { + outline: none !important; + } + + .formattedTextBox-cont { + touch-action: none; + cursor: text; + background: inherit; + padding: 0; + border-width: 0px; + border-radius: inherit; + border-color: $intermediate-color; + box-sizing: border-box; + background-color: inherit; + border-style: solid; + overflow-y: auto; + overflow-x: hidden; + color: initial; + max-height: 100%; + display: flex; + flex-direction: row; + transition: opacity 1s; + + .formattedTextBox-dictation { + height: 12px; + width: 10px; + top: 0px; + left: 0px; + position: absolute; + } + } + + .formattedTextBox-outer { + position: relative; + overflow: auto; + display: inline-block; + width: 100%; + height: 100%; + } + + .formattedTextBox-sidebar-handle { + position: absolute; + top: calc(50% - 17.5px); + width: 10px; + height: 35px; + background: lightgray; + border-radius: 20px; + cursor:grabbing; + } + + .formattedTextBox-cont>.formattedTextBox-sidebar-handle { + right: 0; + left: unset; + } + + .formattedTextBox-sidebar, + .formattedTextBox-sidebar-inking { + border-left: dashed 1px black; + height: 100%; + display: inline-block; + position: absolute; + right: 0; + + .collectionfreeformview-container { + position: relative; + } + + >.formattedTextBox-sidebar-handle { + right: unset; + left: -5; + } + } + + .formattedTextBox-sidebar-inking { + pointer-events: all; + } + + .formattedTextBox-inner-rounded { + height: 70%; + width: 85%; + position: absolute; + overflow: auto; + top: 15%; + left: 10%; + } + + .formattedTextBox-inner-rounded, + .formattedTextBox-inner { + height: 100%; + white-space: pre-wrap; + hr { + display: block; + unicode-bidi: isolate; + margin-block-start: 0.5em; + margin-block-end: 0.5em; + margin-inline-start: auto; + margin-inline-end: auto; + overflow: hidden; + border-style: inset; + border-width: 1px; + } + } + + // .menuicon { + // display: inline-block; + // border-right: 1px solid rgba(0, 0, 0, 0.2); + // color: #888; + // line-height: 1; + // padding: 0 7px; + // margin: 1px; + // cursor: pointer; + // text-align: center; + // min-width: 1.4em; + // } + + .strong, + .heading { + font-weight: bold; + } + + .em { + font-style: italic; + } + + .userMarkOpen { + background: rgba(255, 255, 0, 0.267); + display: inline; + } + + .userMark { + background: rgba(255, 255, 0, 0.267); + font-size: 2px; + display: inline-grid; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 10px; + min-height: 10px; + text-align: center; + align-content: center; + } + + footnote { + display: inline-block; + position: relative; + cursor: pointer; + + div { + padding: 0 !important; + } + } + + footnote::after { + content: counter(prosemirror-footnote); + vertical-align: super; + font-size: 75%; + counter-increment: prosemirror-footnote; + } + + .ProseMirror { + counter-reset: prosemirror-footnote; + } + + .footnote-tooltip { + cursor: auto; + font-size: 75%; + position: absolute; + left: -30px; + top: calc(100% + 10px); + background: silver; + padding: 3px; + border-radius: 2px; + max-width: 100px; + min-width: 50px; + width: max-content; + } + + .prosemirror-attribution { + font-size: 8px; + } + + .footnote-tooltip::before { + border: 5px solid silver; + border-top-width: 0px; + border-left-color: transparent; + border-right-color: transparent; + position: absolute; + top: -5px; + left: 27px; + content: " "; + height: 0; + width: 0; + } + + + .formattedTextBox-inlineComment { + position: relative; + width: 40px; + height: 20px; + &::before { + content: "→"; + } + &:hover { + background: orange; + } + } + + .formattedTextBox-summarizer { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "←"; + } + } + + .formattedTextBox-summarizer-collapsed { + opacity: 0.5; + position: relative; + width: 40px; + height: 20px; + &::after { + content: "..."; + } + } + + .ProseMirror { + touch-action: none; + span { + font-family: inherit; + } + + ol, ul { + counter-reset: deci1 0 multi1 0; + padding-left: 1em; + font-family: inherit; + } + ol { + margin-left: 1em; + font-family: inherit; + } + + .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } + .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} + .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} + .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} + .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } + .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } + .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } + + .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } + .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} + .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} + .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} + + .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } + .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } + .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } + .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } + .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } + .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } + .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } + + .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } + .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } + .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } + .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } + } +} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2cb55e0fa..69708e7a0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -8,7 +8,7 @@ import { baseKeymap, selectAll } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { inputRules } from 'prosemirror-inputrules'; import { keymap } from "prosemirror-keymap"; -import { Fragment, Mark, Node, Slice } from "prosemirror-model"; +import { Fragment, Mark, Node, Slice, Schema } from "prosemirror-model"; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; @@ -16,6 +16,7 @@ import { DateField } from '../../../../fields/DateField'; import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; import applyDevTools = require("prosemirror-dev-tools"); +import { removeMarkWithAttrs } from "./prosemirrorPatches"; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; @@ -32,7 +33,7 @@ import { DocumentType } from '../../../documents/DocumentTypes'; import { DictationManager } from '../../../util/DictationManager'; import { DragManager } from "../../../util/DragManager"; import { makeTemplate } from '../../../util/DropConverter'; -import buildKeymap from "./ProsemirrorExampleTransfer"; +import buildKeymap, { updateBullets } from "./ProsemirrorExampleTransfer"; import RichTextMenu from './RichTextMenu'; import { RichTextRules } from "./RichTextRules"; @@ -56,7 +57,7 @@ import { DocumentButtonBar } from '../../DocumentButtonBar'; import { AudioBox } from '../AudioBox'; import { FieldView, FieldViewProps } from "../FieldView"; import "./FormattedTextBox.scss"; -import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; +import { FormattedTextBoxComment, formattedTextBoxCommentPlugin, findLinkMark } from './FormattedTextBoxComment'; import React = require("react"); library.add(faEdit); @@ -68,15 +69,10 @@ export interface FormattedTextBoxProps { xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yMargin?: number; } - -const richTextSchema = createSchema({ - documentText: "string", -}); - export const GoogleRef = "googleDocId"; -type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; -const RichTextDocument = makeInterface(richTextSchema, documentSchema); +type RichTextDocument = makeInterface<[typeof documentSchema]>; +const RichTextDocument = makeInterface(documentSchema); type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @@ -86,14 +82,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; public ProseRef?: HTMLDivElement; + public get EditorView() { return this._editorView; } private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; private _applyingChange: boolean = false; private _searchIndex = 0; + private _cachedLinks: Doc[] = []; private _undoTyping?: UndoManager.Batch; private _disposers: { [name: string]: IReactionDisposer } = {}; - private dropDisposer?: DragManager.DragDropDisposer; + private _dropDisposer?: DragManager.DragDropDisposer; @computed get _recording() { return this.dataDoc.audioState === "recording"; } set _recording(value) { this.dataDoc.audioState = value ? "recording" : undefined; } @@ -145,6 +143,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + // removes all hyperlink anchors for the removed linkDoc + // TODO: bcz: Argh... if a section of text has multiple anchors, this should just remove the intended one. + // but since removing one anchor from the list of attr anchors isn't implemented, this will end up removing nothing. + public RemoveLinkFromDoc(linkDoc?: Doc) { + const state = this._editorView?.state; + if (state && linkDoc && this._editorView) { + var allLinks: any[] = []; + state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node: any, pos: number, parent: any) => { + const foundMark = findLinkMark(node.marks); + const newHrefs = foundMark?.attrs.allLinks.filter((a: any) => a.href.includes(linkDoc[Id])) || []; + allLinks = newHrefs.length ? newHrefs : allLinks; + return true; + }); + if (allLinks.length) { + this._editorView.dispatch(removeMarkWithAttrs(state.tr, 0, state.doc.nodeSize - 2, state.schema.marks.linkAnchor, { allLinks })); + } + } + } + // removes all the specified link referneces from the selection. + // NOTE: as above, this won't work correctly if there are marks with overlapping but not exact sets of link references. + public RemoveLinkFromSelection(allLinks: { href: string, title: string, linkId: string, targetId: string }[]) { + const state = this._editorView?.state; + if (state && this._editorView) { + this._editorView.dispatch(removeMarkWithAttrs(state.tr, state.selection.from, state.selection.to, state.schema.marks.link, { allLinks })); + } + } + linkOnDeselect: Map<string, string> = new Map(); doLinkOnDeselect() { @@ -181,8 +206,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.linkOnDeselect.set(key, value); const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const allHrefs = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }]; - const link = this._editorView.state.schema.marks.link.create({ allHrefs, location: "onRight", title: value }); + const allLinks = [{ href: Utils.prepend("/doc/" + id), title: value, targetId: id }]; + const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, location: "onRight", title: value }); const mval = this._editorView.state.schema.marks.metadataVal.create(); const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); @@ -250,8 +275,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const lastSel = Math.min(flattened.length - 1, this._searchIndex); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; const alink = DocUtils.MakeLink({ doc: this.rootDoc }, { doc: target }, "automatic")!; - const allHrefs = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }]; - const link = this._editorView.state.schema.marks.link.create({ allHrefs, title: "a link", location }); + const allLinks = [{ href: Utils.prepend("/doc/" + alink[Id]), title: "a link", targetId: target[Id], linkId: alink[Id] }]; + const link = this._editorView.state.schema.marks.linkAnchor.create({ allLinks, title: "a link", location }); this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); } } @@ -271,7 +296,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } public unhighlightSearchTerms = () => { - if (this._editorView && (this._editorView as any).docView) { + if (window.screen.width < 600) null; + else if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const end = this._editorView.state.doc.nodeSize - 2; @@ -291,8 +317,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } protected createDropTarget = (ele: HTMLDivElement) => { this.ProseRef = ele; - this.dropDisposer?.(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); + this._dropDisposer?.(); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.layoutDoc)); } @undoBatch @@ -618,9 +644,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp let tr = state.tr.addMark(sel.from, sel.to, splitter); sel.from !== sel.to && tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { - const allHrefs = [{ href, title, targetId, linkId }]; - allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? [])); - const link = state.schema.marks.link.create({ allHrefs, title, location, linkId }); + const allLinks = [{ href, title, targetId, linkId }]; + allLinks.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.linkAnchor.name)?.attrs.allLinks ?? [])); + const link = state.schema.marks.linkAnchor.create({ allLinks, title, location, linkId }); tr = tr.addMark(pos, pos + node.nodeSize, link); } }); @@ -630,6 +656,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } componentDidMount() { + this._cachedLinks = DocListCast(this.Document.links); + this._disposers.links = reaction(() => DocListCast(this.Document.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + newLinks => { + this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); + this._cachedLinks = newLinks; + }); this._disposers.buttonBar = reaction( () => DocumentButtonBar.Instance, instance => { @@ -660,8 +692,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp incomingValue => { if (incomingValue !== undefined && this._editorView && !this._applyingChange) { const updatedState = JSON.parse(incomingValue); - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); + if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { + this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + this.tryUpdateHeight(); + } } } ); @@ -733,8 +767,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return node.copy(content.frag); } const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); - return linkIndex !== -1 && marks[linkIndex].attrs.allHrefs.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.linkAnchor); + return linkIndex !== -1 && marks[linkIndex].attrs.allLinks.find((item: { href: string }) => scrollToLinkID === item.href.replace(/.*\/doc\//, "")) ? node : undefined; }; let start = 0; @@ -916,8 +950,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } const marks = [...node.marks]; const linkIndex = marks.findIndex(mark => mark.type.name === "link"); - const allHrefs = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }]; - const link = view.state.schema.mark(view.state.schema.marks.link, { allHrefs, location: "onRight", title, docref: true }); + const allLinks = [{ href: Utils.prepend(`/doc/${linkId}`), title, linkId }]; + const link = view.state.schema.mark(view.state.schema.marks.linkAnchor, { allLinks, location: "onRight", title, docref: true }); marks.splice(linkIndex === -1 ? 0 : linkIndex, 1, link); return node.mark(marks); } @@ -1140,7 +1174,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); const view = this._editorView as any; - // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there + // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. if (view.mouseDown) { const originalUpHandler = view.mouseDown.up; @@ -1163,18 +1197,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); } - public static HadSelection: boolean = false; - onBlur = (e: any) => { - FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; - //DictationManager.Controls.stop(false); + public startUndoTypingBatch() { + this._undoTyping = UndoManager.StartBatch("undoTyping"); + } + + public endUndoTypingBatch() { + const wasUndoing = this._undoTyping; if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; } + return wasUndoing; + } + public static HadSelection: boolean = false; + onBlur = (e: any) => { + FormattedTextBox.HadSelection = window.getSelection()?.toString() !== ""; + //DictationManager.Controls.stop(false); + this.endUndoTypingBatch(); this.doLinkOnDeselect(); // move the richtextmenu offscreen - if (!RichTextMenu.Instance.Pinned && !RichTextMenu.Instance.overMenu) RichTextMenu.Instance.jumpTo(-300, -300); + if (!RichTextMenu.Instance.Pinned) RichTextMenu.Instance.delayHide(); } _lastTimedMark: Mark | undefined = undefined; @@ -1208,11 +1251,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); if (!this._undoTyping) { - this._undoTyping = UndoManager.StartBatch("undoTyping"); + this.startUndoTypingBatch(); } } ondrop = (eve: React.DragEvent) => { + this._editorView!.dispatch(updateBullets(this._editorView!.state.tr, this._editorView!.state.schema)); eve.stopPropagation(); // drag n drop of text within text note will generate a new note if not caughst, as will dragging in from outside of Dash. } onscrolled = (ev: React.UIEvent) => { @@ -1280,7 +1324,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp color: this.props.color ? this.props.color : StrCast(this.layoutDoc[this.props.fieldKey + "-color"], this.props.hideOnLeave ? "white" : "inherit"), pointerEvents: interactive ? undefined : "none", fontSize: Cast(this.layoutDoc._fontSize, "number", null), - fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit") + fontFamily: StrCast(this.layoutDoc._fontFamily, "inherit"), + transition: "opacity 1s" }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} @@ -1308,7 +1353,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onScroll={this.onscrolled} onDrop={this.ondrop} > <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget} style={{ - padding: this.layoutDoc._textBoxPadding ? this.layoutDoc._textBoxPadding : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`, + padding: this.layoutDoc._textBoxPadding ? StrCast(this.layoutDoc._textBoxPadding) : `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`, pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined }} /> diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 90f2c0aa6..4c90b6afd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -27,7 +27,7 @@ export function findUserMark(marks: Mark[]): Mark | undefined { return marks.find(m => m.attrs.userid); } export function findLinkMark(marks: Mark[]): Mark | undefined { - return marks.find(m => m.type === schema.marks.link); + return marks.find(m => m.type === schema.marks.linkAnchor); } export function findStartOfMark(rpos: ResolvedPos, view: EditorView, finder: (marks: Mark[]) => Mark | undefined) { let before = 0; @@ -182,7 +182,7 @@ export class FormattedTextBoxComment { state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); child = child || (nbef && state.selection.$from.nodeBefore); const mark = child ? findLinkMark(child.marks) : undefined; - const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allHrefs.find((item: { href: string }) => item.href)?.href || forceUrl; + const href = (!mark?.attrs.docref || naft === nbef) && mark?.attrs.allLinks.find((item: { href: string }) => item.href)?.href || forceUrl; if (forceUrl || (href && child && nbef && naft && mark?.attrs.showPreview)) { FormattedTextBoxComment.tooltipText.textContent = "external => " + href; (FormattedTextBoxComment.tooltipText as any).href = href; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.scss b/src/client/views/nodes/formattedText/RichTextMenu.scss index 7a0718c16..fbc468292 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.scss +++ b/src/client/views/nodes/formattedText/RichTextMenu.scss @@ -77,6 +77,12 @@ color: white; } } + .richTextMenu-divider { + margin: auto; + border-left: solid #ffffff70 0.5px; + height: 20px; + width: 1px; + } } .link-menu { diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index a06fcec9a..95d6c9fac 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -1,8 +1,8 @@ import React = require("react"); import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faIndent, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons"; +import { faBold, faCaretDown, faChevronLeft, faEyeDropper, faHighlighter, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faItalic, faLink, faPaintRoller, faPalette, faStrikethrough, faSubscript, faSuperscript, faUnderline } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, observable } from "mobx"; +import { action, observable, IReactionDisposer, reaction } from "mobx"; import { observer } from "mobx-react"; import { lift, wrapIn } from "prosemirror-commands"; import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos } from "prosemirror-model"; @@ -23,9 +23,10 @@ import { updateBullets } from "./ProsemirrorExampleTransfer"; import "./RichTextMenu.scss"; import { schema } from "./schema_rts"; import { TraceMobx } from "../../../../fields/util"; +import { UndoManager } from "../../../util/UndoManager"; const { toggleMark } = require("prosemirror-commands"); -library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); +library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faOutdent, faIndent, faHandPointLeft, faHandPointRight, faEyeDropper, faCaretDown, faPalette, faHighlighter, faLink, faPaintRoller); @observer @@ -68,6 +69,8 @@ export default class RichTextMenu extends AntimodeMenu { @observable private currentLink: string | undefined = ""; @observable private showLinkDropdown: boolean = false; + _reaction: IReactionDisposer | undefined; + _delayHide = false; constructor(props: Readonly<{}>) { super(props); RichTextMenu.Instance = this; @@ -138,6 +141,16 @@ export default class RichTextMenu extends AntimodeMenu { ]; } + componentDidMount() { + this._reaction = reaction(() => SelectionManager.SelectedDocuments(), + () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true)); + } + componentWillUnmount() { + this._reaction?.(); + } + + public delayHide = () => this._delayHide = true; + @action changeView(view: EditorView) { this.view = view; @@ -147,16 +160,6 @@ export default class RichTextMenu extends AntimodeMenu { this.updateFromDash(view, lastState, this.editorProps); } - public MakeLinkToSelection = (linkDocId: string, title: string, location: string, targetDocId: string): string => { - if (this.view) { - const link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + linkDocId), title: title, location: location, linkId: linkDocId, targetId: targetDocId }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). - addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - return this.view.state.selection.$from.nodeAfter?.text || ""; - } - return ""; - } - @action public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { if (!view) { @@ -310,8 +313,11 @@ export default class RichTextMenu extends AntimodeMenu { function onClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && command && command(self.view.state, self.view.dispatch, self.view); - self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => { + self.view && command && command(self.view.state, self.view.dispatch, self.view); + self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); + }, "rich text menu command"); self.setActiveMarkButtons(self.getActiveMarksOnSelection()); } @@ -338,9 +344,10 @@ export default class RichTextMenu extends AntimodeMenu { function onChange(e: React.ChangeEvent<HTMLSelectElement>) { e.stopPropagation(); e.preventDefault(); + self.TextView.endUndoTypingBatch(); options.forEach(({ label, mark, command }) => { if (e.target.value === label) { - self.view && mark && command(mark, self.view); + UndoManager.RunInBatch(() => self.view && mark && command(mark, self.view), "text mark dropdown"); } }); } @@ -361,9 +368,10 @@ export default class RichTextMenu extends AntimodeMenu { const self = this; function onChange(val: string) { + self.TextView.endUndoTypingBatch(); options.forEach(({ label, node, command }) => { if (val === label) { - self.view && node && command(node); + UndoManager.RunInBatch(() => self.view && node && command(node), "nodes dropdown"); } }); } @@ -435,6 +443,63 @@ export default class RichTextMenu extends AntimodeMenu { return true; } + insetParagraph(state: EditorState<any>, dispatch: any) { + var tr = state.tr; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + if (node.type === schema.nodes.paragraph) { + const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10; + tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks); + return false; + } + return true; + }); + dispatch?.(tr); + return true; + } + outsetParagraph(state: EditorState<any>, dispatch: any) { + var tr = state.tr; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + if (node.type === schema.nodes.paragraph) { + const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10); + tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks); + return false; + } + return true; + }); + dispatch?.(tr); + return true; + } + + indentParagraph(state: EditorState<any>, dispatch: any) { + var tr = state.tr; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + if (node.type === schema.nodes.paragraph) { + const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined; + const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25; + tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks); + return false; + } + return true; + }); + dispatch?.(tr); + return true; + } + + hangingIndentParagraph(state: EditorState<any>, dispatch: any) { + var tr = state.tr; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + if (node.type === schema.nodes.paragraph) { + const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined; + const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10; + tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks); + return false; + } + return true; + }); + dispatch?.(tr); + return true; + } + insertBlockquote(state: EditorState<any>, dispatch: any) { const path = (state.selection.$from as any).path; if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) { @@ -445,6 +510,11 @@ export default class RichTextMenu extends AntimodeMenu { return true; } + insertHorizontalRule(state: EditorState<any>, dispatch: any) { + dispatch(state.tr.replaceSelectionWith(state.schema.nodes.horizontal_rule.create()).scrollIntoView()); + return true; + } + @action toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; } // todo: add brushes to brushMap to save with a style name @@ -461,7 +531,8 @@ export default class RichTextMenu extends AntimodeMenu { function onBrushClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.fillBrush(self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.fillBrush(self.view.state, self.view.dispatch), "rt brush"); } let label = "Stored marks: "; @@ -528,19 +599,24 @@ export default class RichTextMenu extends AntimodeMenu { @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; } @action setActiveColor(color: string) { this.activeFontColor = color; } + get TextView() { return (this.view as any).TextView as FormattedTextBox; } createColorButton() { const self = this; function onColorClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); + self.TextView.EditorView!.focus(); } function changeColor(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); self.setActiveColor(color); - self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch), "rt menu color"); + self.TextView.EditorView!.focus(); } const button = @@ -585,13 +661,15 @@ export default class RichTextMenu extends AntimodeMenu { function onHighlightClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highligher"); } function changeHighlight(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); self.setActiveHighlight(color); - self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch), "rt highlighter"); } const button = @@ -631,7 +709,8 @@ export default class RichTextMenu extends AntimodeMenu { const self = this; function onLinkChange(e: React.ChangeEvent<HTMLInputElement>) { - self.setCurrentLink(e.target.value); + self.TextView.endUndoTypingBatch(); + UndoManager.RunInBatch(() => self.setCurrentLink(e.target.value), "link change"); } const link = this.currentLink ? this.currentLink : ""; @@ -658,7 +737,7 @@ export default class RichTextMenu extends AntimodeMenu { const node = this.view.state.selection.$from.nodeAfter; const link = node && node.marks.find(m => m.type.name === "link"); if (link) { - const href = link.attrs.allHrefs.length > 0 ? link.attrs.allHrefs[0].href : undefined; + const href = link.attrs.allLinks.length > 0 ? link.attrs.allLinks[0].href : undefined; if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; @@ -693,40 +772,28 @@ export default class RichTextMenu extends AntimodeMenu { } deleteLink = () => { - if (!this.view) return; - - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type === this.view!.state.schema.marks.link); - const href = link!.attrs.allHrefs.length > 0 ? link!.attrs.allHrefs[0].href : undefined; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - DocServer.GetRefField(linkclicked).then(async linkDoc => { - if (linkDoc instanceof Doc) { - LinkManager.Instance.deleteLink(linkDoc); - this.view!.dispatch(this.view!.state.tr.removeMark(this.view!.state.selection.from, this.view!.state.selection.to, this.view!.state.schema.marks.link)); - } - }); - } - } else { - if (node) { - const { tr, schema, selection } = this.view.state; - const extension = this.linkExtend(selection.$anchor, href); - this.view.dispatch(tr.removeMark(extension.from, extension.to, schema.marks.link)); - } + if (this.view) { + const link = this.view.state.selection.$from.nodeAfter?.marks.find(m => m.type === this.view!.state.schema.marks.linkAnchor); + if (link) { + const allLinks = link.attrs.allLinks.slice(); + this.TextView.RemoveLinkFromSelection(link.attrs.allLinks); + // bcz: Argh ... this will remove the link from the document even it's anchored somewhere else in the text which happens if only part of the anchor text was selected. + allLinks.filter((aref: any) => aref?.href.indexOf(Utils.prepend("/doc/")) === 0).forEach((aref: any) => { + const linkId = aref.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + linkId && DocServer.GetRefField(linkId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); + }); } } } linkExtend($start: ResolvedPos, href: string) { - const mark = this.view!.state.schema.marks.link; + const mark = this.view!.state.schema.marks.linkAnchor; let startIndex = $start.index(); let endIndex = $start.indexAfter(); - while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) startIndex--; - while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allHrefs.find((item: { href: string }) => item.href === href)).length) endIndex++; + while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) startIndex--; + while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allLinks.find((item: { href: string }) => item.href === href)).length) endIndex++; let startPos = $start.start(); let endPos = startPos; @@ -766,7 +833,7 @@ export default class RichTextMenu extends AntimodeMenu { return ref_node; } - @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = true; } + @action onPointerEnter(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } @action onPointerLeave(e: React.PointerEvent) { RichTextMenu.Instance.overMenu = false; } @action @@ -797,24 +864,34 @@ export default class RichTextMenu extends AntimodeMenu { this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)), this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)), this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)), + <div className="richTextMenu-divider" /> ]}</>, this.createColorButton(), this.createHighlighterButton(), this.createLinkButton(), this.createBrushButton(), + <div className="richTextMenu-divider" />, this.createButton("align-left", "Align Left", undefined, this.alignLeft), this.createButton("align-center", "Align Center", undefined, this.alignCenter), this.createButton("align-right", "Align Right", undefined, this.alignRight), + this.createButton("indent", "Inset More", undefined, this.insetParagraph), + this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph), + this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph), + this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph), ]}</div>; const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2"> {this.collapsed ? this.getDragger() : (null)} <div key="row" style={{ display: this.collapsed ? "none" : undefined }}> + <div className="richTextMenu-divider" />, {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), + <div className="richTextMenu-divider" />, this.createNodesDropdown(this.activeListType, this.listTypeOptions, "nodes"), this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer), - this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),]} + this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote), + this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule), + <div className="richTextMenu-divider" />,]} </div> <div key="button"> {/* <div key="collapser"> @@ -844,7 +921,7 @@ interface ButtonDropdownProps { } @observer -class ButtonDropdown extends React.Component<ButtonDropdownProps> { +export class ButtonDropdown extends React.Component<ButtonDropdownProps> { @observable private showDropdown: boolean = false; private ref: HTMLDivElement | null = null; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 612a824fc..ca30dde9d 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -279,7 +279,7 @@ export class RichTextRules { DocUtils.Publish(target, docid, returnFalse, returnFalse); DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to"); }); - const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); + const link = state.schema.marks.linkAnchor.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); } return state.tr; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 54b61aa20..3d7d71b14 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -17,12 +17,12 @@ export const marks: { [index: string]: MarkSpec } = { return ["div", { className: "dummy" }, 0]; } }, - // :: MarkSpec A link. Has `href` and `title` attributes. `title` + // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each link has an href URL and a title for use in menus and hover (Dash links have linkIDs & targetIDs). `title` // defaults to the empty string. Rendered and parsed as an `<a>` // element. - link: { + linkAnchor: { attrs: { - allHrefs: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] }, + allLinks: { default: [] as { href: string, title: string, linkId: string, targetId: string }[] }, showPreview: { default: true }, location: { default: null }, title: { default: null }, @@ -31,22 +31,22 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: false, parseDOM: [{ tag: "a[href]", getAttrs(dom: any) { - return { allHrefs: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), }; + return { allLinks: [{ href: dom.getAttribute("href"), title: dom.getAttribute("title"), linkId: dom.getAttribute("linkids"), targetId: dom.getAttribute("targetids") }], location: dom.getAttribute("location"), }; } }], toDOM(node: any) { - const targetids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, ""); - const linkids = node.attrs.allHrefs.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, ""); + const targetids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.targetId, ""); + const linkids = node.attrs.allLinks.reduce((p: string, item: { href: string, title: string, targetId: string, linkId: string }) => p + " " + item.linkId, ""); return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allHrefs[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - node.attrs.allHrefs.length === 1 ? - ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allHrefs[0].href }, 0] : + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + node.attrs.allLinks.length === 1 ? + ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] : ["div", { class: "prosemirror-anchor" }, ["span", { class: "prosemirror-linkBtn" }, ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0], ["input", { class: "prosemirror-hrefoptions" }], ], - ["div", { class: "prosemirror-links" }, ...node.attrs.allHrefs.map((item: { href: string, title: string }) => + ["div", { class: "prosemirror-links" }, ...node.attrs.allLinks.map((item: { href: string, title: string }) => ["a", { class: "prosemirror-dropdownlink", href: item.href }, item.title] )] ]; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index afb1f57b7..f83cff9b9 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -312,7 +312,7 @@ export const nodes: { [index: string]: NodeSpec } = { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; return node.attrs.visibility ? ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, 0] : - ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, "..."]; + ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, `${node.firstChild?.textContent}...`]; } }, };
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/prosemirrorPatches.js b/src/client/views/nodes/formattedText/prosemirrorPatches.js index 763961958..0969ea4ef 100644 --- a/src/client/views/nodes/formattedText/prosemirrorPatches.js +++ b/src/client/views/nodes/formattedText/prosemirrorPatches.js @@ -9,6 +9,7 @@ var prosemirrorModel = require('prosemirror-model'); exports.liftListItem = liftListItem; exports.sinkListItem = sinkListItem; exports.wrappingInputRule = wrappingInputRule; +exports.removeMarkWithAttrs = removeMarkWithAttrs; // :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool // Create a command to lift the list item around the selection up into // a wrapping list. @@ -139,3 +140,57 @@ function wrappingInputRule(regexp, nodeType, getAttrs, joinPredicate, customWith } +// :: ([Mark]) → ?Mark +// Tests whether there is a mark of this type in the given set. +function isInSetWithAttrs(mark, set, attrs) { + for (var i = 0; i < set.length; i++) { + if (set[i].type == mark) { + if (Array.from(Object.keys(attrs)).reduce((p, akey) => { + return p && JSON.stringify(set[i].attrs[akey]) === JSON.stringify(attrs[akey]); + }, true)) { + return set[i]; + } + } + } +}; + +// :: (number, number, ?union<Mark, MarkType>) → this +// Remove marks from inline nodes between `from` and `to`. When `mark` +// is a single mark, remove precisely that mark. When it is a mark type, +// remove all marks of that type. When it is null, remove all marks of +// any type. +function removeMarkWithAttrs(tr, from, to, mark, attrs) { + if (mark === void 0) mark = null; + + var matched = [], step = 0; + tr.doc.nodesBetween(from, to, function (node, pos) { + if (!node.isInline) { return } + step++; + var toRemove = null; + if (mark) { + if (isInSetWithAttrs(mark, node.marks, attrs)) { toRemove = [mark]; } + } else { + toRemove = node.marks; + } + if (toRemove && toRemove.length) { + var end = Math.min(pos + node.nodeSize, to); + for (var i = 0; i < toRemove.length; i++) { + var style = toRemove[i], found$1 = (void 0); + for (var j = 0; j < matched.length; j++) { + var m = matched[j]; + if (m.step == step - 1 && style.eq(matched[j].style)) { found$1 = m; } + } + if (found$1) { + found$1.to = end; + found$1.step = step; + } else { + matched.push({ style: style, from: Math.max(pos, from), to: end, step: step }); + } + } + } + }); + matched.forEach(function (m) { return tr.step(new prosemirrorTransform.RemoveMarkStep(m.from, m.to, m.style)); }); + return tr +}; + + diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss index 3c08ba80d..fa43a99b2 100644 --- a/src/client/views/pdf/PDFMenu.scss +++ b/src/client/views/pdf/PDFMenu.scss @@ -3,4 +3,23 @@ width: 200px; padding: 5px; grid-template-columns: 90px 20px 90px; +} + +.color-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + button.color-button { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: 2px solid transparent !important; + padding: 3px; + + &.active { + border: 2px solid white; + } + } }
\ No newline at end of file diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 6dcf5cce6..00c56d73e 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -1,22 +1,43 @@ import React = require("react"); import "./PDFMenu.scss"; -import { observable, action, } from "mobx"; +import { observable, action, computed, } from "mobx"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { unimplementedFunction, returnFalse } from "../../../Utils"; +import { unimplementedFunction, returnFalse, Utils } from "../../../Utils"; import AntimodeMenu from "../AntimodeMenu"; import { Doc, Opt } from "../../../fields/Doc"; +import { ColorState } from "react-color"; +import { ButtonDropdown } from "../nodes/formattedText/RichTextMenu"; + @observer export default class PDFMenu extends AntimodeMenu { static Instance: PDFMenu; private _commentCont = React.createRef<HTMLButtonElement>(); + private _palette = [ + "rgba(208, 2, 27, 0.8)", + "rgba(238, 0, 0, 0.8)", + "rgba(245, 166, 35, 0.8)", + "rgba(248, 231, 28, 0.8)", + "rgba(245, 230, 95, 0.616)", + "rgba(139, 87, 42, 0.8)", + "rgba(126, 211, 33, 0.8)", + "rgba(65, 117, 5, 0.8)", + "rgba(144, 19, 254, 0.8)", + "rgba(238, 169, 184, 0.8)", + "rgba(224, 187, 228, 0.8)", + "rgba(225, 223, 211, 0.8)", + "rgba(255, 255, 255, 0.8)", + "rgba(155, 155, 155, 0.8)", + "rgba(0, 0, 0, 0.8)"]; @observable private _keyValue: string = ""; @observable private _valueValue: string = ""; @observable private _added: boolean = false; + @observable private highlightColor: string = "rgba(245, 230, 95, 0.616)"; + @observable public _colorBtn = false; @observable public Highlighting: boolean = false; @observable public Status: "pdf" | "annotation" | "" = ""; @@ -70,11 +91,47 @@ export default class PDFMenu extends AntimodeMenu { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight + if (!this.Highlight(this.highlightColor) && this.Pinned) { this.Highlighting = !this.Highlighting; } } + @computed get highlighter() { + const button = + <button className="antimodeMenu-button color-preview-button" title="" key="highilghter-button" onPointerDown={this.highlightClicked}> + <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /> + <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div> + </button>; + + const dropdownContent = + <div className="dropdown"> + <p>Change highlighter color:</p> + <div className="color-wrapper"> + {this._palette.map(color => { + if (color) { + return this.highlightColor === color ? + <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> : + <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button>; + } + })} + </div> + </div>; + return ( + <ButtonDropdown key={"highlighter"} button={button} dropdownContent={dropdownContent} /> + ); + } + + @action + changeHighlightColor = (color: string, e: React.PointerEvent) => { + const col: ColorState = { + hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" }, + rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "", + }; + e.preventDefault(); + e.stopPropagation(); + this.highlightColor = Utils.colorString(col); + } + deleteClicked = (e: React.PointerEvent) => { this.Delete(); } @@ -101,12 +158,11 @@ export default class PDFMenu extends AntimodeMenu { render() { const buttons = this.Status === "pdf" ? [ - <button key="1" className="antimodeMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}> - <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>, + this.highlighter, <button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}> <FontAwesomeIcon icon="comment-alt" size="lg" /></button>, <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}> - <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button> + <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /></button>, ] : [ <button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}> <FontAwesomeIcon icon="trash-alt" size="lg" /></button>, diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 4fdc82388..4e5fdbfbb 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -97,7 +97,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @observable private _zoomed = 1; private _pdfViewer: any; - private _retries = 0; // number of times tried to create the PDF viewer + private _retries = 0; // number of times tried to create the PDF viewer private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _reactionDisposer?: IReactionDisposer; @@ -521,7 +521,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu if (this._marqueeing) { if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { const marquees = this._mainCont.current!.getElementsByClassName("pdfViewerDash-dragAnnotationBox"); - if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation. + if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation. const style = (marquees[0] as HTMLDivElement).style; const copy = document.createElement("div"); copy.style.left = style.left; @@ -691,7 +691,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu </div>; } @computed get pdfViewerDiv() { - return <div className={"pdfViewerDash-text" + ((!DocumentDecorations.Instance.Interacting && (this.props.isSelected() || this.props.isChildActive())) ? "-selected" : "")} ref={this._viewer} />; + return <div className={"pdfViewerDash-text" + ((!DocumentDecorations.Instance?.Interacting && (this.props.isSelected() || this.props.isChildActive())) ? "-selected" : "")} ref={this._viewer} />; } @computed get contentScaling() { return this.props.ContentScaling(); } @computed get standinViews() { @@ -713,8 +713,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ overflowX: this._zoomed !== 1 ? "scroll" : undefined, - width: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`, - height: !this.props.Document._fitWidth ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`, + width: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeWidth) : `${100 / this.contentScaling}%`, + height: !this.props.Document._fitWidth && (window.screen.width > 600) ? NumCast(this.props.Document._nativeHeight) : `${100 / this.contentScaling}%`, transform: `scale(${this.props.ContentScaling()})` }} > {this.pdfViewerDiv} @@ -746,4 +746,4 @@ class PdfViewerMarquee extends React.Component<PdfViewerMarqueeProps> { }}> </div>; } -}
\ No newline at end of file +} diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss index ccd2e8947..dbe6b0d4f 100644 --- a/src/client/views/presentationview/PresElementBox.scss +++ b/src/client/views/presentationview/PresElementBox.scss @@ -13,9 +13,10 @@ -moz-user-select: none; -ms-user-select: none; user-select: none; - transition: all .1s; + transition: all .1s; padding: 0px; padding-bottom: 3px; + .documentView-node { position: absolute; z-index: 1; @@ -45,7 +46,7 @@ .presElementBox-closeIcon { border-radius: 20px; - transform:scale(0.7); + transform: scale(0.7); position: absolute; right: 0; top: 0; @@ -58,6 +59,7 @@ position: relative; width: 100%; height: auto; + .presElementBox-interaction { color: gray; float: left; @@ -65,6 +67,7 @@ width: 20px; height: 20px; } + .presElementBox-interaction-selected { color: white; float: left; @@ -76,7 +79,7 @@ } .presElementBox-name { - font-size: 12pxππ; + font-size: 12px; position: absolute; display: inline-block; width: calc(100% - 45px); @@ -90,15 +93,58 @@ display: flex; width: auto; justify-content: center; - margin:auto; + margin: auto; } .presElementBox-embeddedMask { - width:100%; - height:100%; + width: 100%; + height: 100%; position: absolute; - left:0; - top:0; + left: 0; + top: 0; background: transparent; - z-index:2; + z-index: 2; +} + +@media only screen and (max-device-width: 480px) { + .presElementBox-buttons { + display: inline-flex; + position: absolute; + top: 0; + right: 0; + z-index: 3; + width: 50%; + + .presElementBox-interaction { + width: 50; + height: 50; + } + + .presElementBox-interaction-selected { + width: 50; + height: 50; + } + } + + .presElementBox-item { + display: inline-flex; + overflow: hidden; + } + + .presElementBox-closeIcon { + transform: scale(1.5); + right: 10; + top: 10; + } + + .presElementBox-name { + font-size: 30px; + top: 10px; + z-index: 3; + width: 50%; + } + + .presElementBox-embedded { + transform: translate(0, 90px) scale(1.5); + } }
\ No newline at end of file diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 6b59a0563..6fd3455b6 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -49,7 +49,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc componentDidMount() { this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], - params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); + params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 200 : 0), { fireImmediately: true }); } componentWillUnmount() { this._heightDisposer?.(); |