diff options
Diffstat (limited to 'src')
30 files changed, 523 insertions, 376 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bb60586eb..652f8e7e0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -139,6 +139,8 @@ export class DocumentOptions { _itemIndex?: number; // which item index the carousel viewer is showing _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) + _minFontSize?: number; // minimum font size for labelBoxes + _maxFontSize?: number; // maximum font size for labelBoxes _columnWidth?: number; _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden _fontSize?: string; @@ -441,7 +443,7 @@ export namespace Docs { }], [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } + options: { links: "@links(self)", _singleLine: true } }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af731ce9f..bf722157a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -9,7 +9,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -292,7 +292,7 @@ export class CurrentUserUtils { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.LabelDocument({ title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); // Docs.Create.TextDocument("", { // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }) @@ -303,8 +303,9 @@ export class CurrentUserUtils { } if (doc["template-icon-view-rtf"] === undefined) { const iconRtfView = Docs.Create.LabelDocument({ - title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + title: "icon_" + DocumentType.RTF, _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), + _singleLine: false, _minFontSize: 18, _maxFontSize: 24, + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); @@ -319,19 +320,41 @@ export class CurrentUserUtils { } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + title: "data", _width: 150, isTemplateDoc: true, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); } if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + const iconColView = Docs.Create.ImageDocument("", { title: "icon", _showTitle: "title", _width: 360 / 4, _height: 270 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); + const proto = iconColView.proto as Doc; + proto["icon-nativeWidth"] = 360 / 4; + proto["icon-nativeHeight"] = 270 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } + if (doc["template-icon-view-video"] === undefined) { + const iconVidView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + iconVidView.isTemplateDoc = makeTemplate(iconVidView, true, "icon_" + DocumentType.VID); + const proto = iconVidView.proto as Doc; + proto["icon-nativeWidth"] = 360 / 4; + proto["icon-nativeHeight"] = 270 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); + doc["template-icon-view-video"] = new PrefetchProxy(iconVidView); + } + if (doc["template-icon-view-pdf"] === undefined) { + const iconPdfView = Docs.Create.ImageDocument("", { title: "icon", _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + iconPdfView.isTemplateDoc = makeTemplate(iconPdfView, true, "icon_" + DocumentType.PDF); + const proto = iconPdfView.proto as Doc; + proto["icon-nativeWidth"] = 360 / 4; + proto["icon-nativeHeight"] = 270 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); + doc["template-icon-view-pdf"] = new PrefetchProxy(iconPdfView); + } if (doc["template-icons"] === undefined) { doc["template-icons"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-icon-view"] as Doc, doc["template-icon-view-img"] as Doc, doc["template-icon-view-button"] as Doc, - doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); + doc["template-icon-view-col"] as Doc, doc["template-icon-view-rtf"] as Doc, doc["template-icon-view-video"] as Doc, doc["template-icon-view-pdf"] as Doc], { title: "icon templates", _height: 75, system: true })); } 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-button"] as Doc, @@ -1321,9 +1344,6 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } - if (doc.activeCollectionBackground === "white") { // temporary to avoid having to rebuild the databse for old accounts that have this set by default. - doc.activeCollectionBackground = undefined; - } return doc; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ad6d90bc3..1d603e18f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -169,7 +169,12 @@ export class DocumentManager { const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; const docView = getFirstDocView(targetDoc, originatingDoc); const wasHidden = targetDoc.hidden; // - if (wasHidden) runInAction(() => targetDoc.hidden = false); // if the target is hidden, un-hide it here. + if (wasHidden) { + runInAction(() => { + targetDoc.hidden = false; + docView?.props.bringToFront(targetDoc); + }); // if the target is hidden, un-hide it here. + } const focusAndFinish = (didFocus: boolean) => { if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 411fc6d11..13e5940d6 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -380,8 +380,8 @@ export namespace DragManager { } } const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / ele.offsetWidth; - const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX; + const scaleX = rect.width / (ele.offsetWidth || rect.width); + const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX; elesCont.left = Math.min(rect.left, elesCont.left); elesCont.top = Math.min(rect.top, elesCont.top); @@ -405,7 +405,7 @@ export namespace DragManager { Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => - ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none")); + (ele as any).style && ((ele as any).style.pointerEvents = "none")); dragDiv.appendChild(dragElement); if (dragElement !== ele) { diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 82dca1287..481b90249 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -4,8 +4,8 @@ $linkGap: 3px; .documentDecorations-Dark, .documentDecorations { - position: absolute; - z-index: 2000; + position: absolute; + z-index: 2000; } .documentDecorations-Dark { background: dimgray; @@ -17,11 +17,11 @@ $linkGap: 3px; left: 0; display: grid; grid-template-rows: 20px 8px 1fr 8px; - grid-template-columns: 8px 16px 1fr 8px 8px; + grid-template-columns: 8px 1fr 8px; pointer-events: none; .documentDecorations-centerCont { - grid-column: 3; + grid-column: 2; background: none; } @@ -41,6 +41,7 @@ $linkGap: 3px; opacity: 1; transform: translate(10px, 10px); grid-row: 4; + grid-column: 3 } .documentDecorations-topLeftResizer, @@ -73,20 +74,18 @@ $linkGap: 3px; .documentDecorations-topResizer, .documentDecorations-bottomResizer { - grid-column-start: 2; - grid-column-end: 5; + grid-column: 2; } .documentDecorations-bottomRightResizer, .documentDecorations-topRightResizer, .documentDecorations-rightResizer { - grid-column-start: 5; - grid-column-end: 7; + grid-column: 3; } .documentDecorations-rotation, .documentDecorations-borderRadius { - grid-column: 5; + grid-column: 3; grid-row: 4; border-radius: 100%; background: black; @@ -132,114 +131,114 @@ $linkGap: 3px; opacity: 1; } - .documentDecorations-topLeftResizer, - .documentDecorations-leftResizer, - .documentDecorations-bottomLeftResizer { - grid-column: 1; - } - - .documentDecorations-topResizer, - .documentDecorations-bottomResizer { - grid-column-start: 2; - grid-column-end: 5; - } - - .documentDecorations-bottomRightResizer, - .documentDecorations-topRightResizer, - .documentDecorations-rightResizer { - grid-column-start: 5; - grid-column-end: 7; - } - - .documentDecorations-rotation, - .documentDecorations-borderRadius { - grid-column: 5; - grid-row: 4; - border-radius: 100%; - background: black; - height: 8; - right: -12; - top: 12; - position: relative; - pointer-events: all; - cursor: nwse-resize; - - .borderRadiusTooltip { - width: 10px; - height: 10px; - position: absolute; - } - } - .documentDecorations-rotation { - background: transparent; - right: -15; - } - - .documentDecorations-topLeftResizer, - .documentDecorations-bottomRightResizer { - cursor: nwse-resize; - background: unset; - opacity: 1; - } + .documentDecorations-topLeftResizer, + .documentDecorations-leftResizer, + .documentDecorations-bottomLeftResizer { + grid-column: 1; + } - .documentDecorations-topLeftResizer { - border-left: 2px solid; - border-top: solid 2px; - } + .documentDecorations-topResizer, + .documentDecorations-bottomResizer { + grid-column: 2; + } - .documentDecorations-bottomRightResizer { - border-right: 2px solid; - border-bottom: solid 2px; - } + .documentDecorations-bottomRightResizer, + .documentDecorations-topRightResizer, + .documentDecorations-rightResizer { + grid-column: 3 + } - .documentDecorations-topLeftResizer:hover, - .documentDecorations-bottomRightResizer:hover { - opacity: 1; - } + .documentDecorations-rotation, + .documentDecorations-borderRadius { + grid-column: 3; + grid-row: 4; + border-radius: 100%; + background: black; + height: 8; + right: -12; + top: 12; + position: relative; + pointer-events: all; + cursor: nwse-resize; - .documentDecorations-bottomRightResizer { - grid-row: 4; - } + .borderRadiusTooltip { + width: 10px; + height: 10px; + position: absolute; + } + } + .documentDecorations-rotation { + background: transparent; + right: -15; + } - .documentDecorations-topRightResizer, - .documentDecorations-bottomLeftResizer { - cursor: nesw-resize; - background: unset; - opacity: 1; - } + .documentDecorations-topLeftResizer, + .documentDecorations-bottomRightResizer { + cursor: nwse-resize; + background: unset; + opacity: 1; + } + + .documentDecorations-topLeftResizer { + border-left: 2px solid; + border-top: solid 2px; + } + + .documentDecorations-bottomRightResizer { + border-right: 2px solid; + border-bottom: solid 2px; + } + + .documentDecorations-topLeftResizer:hover, + .documentDecorations-bottomRightResizer:hover { + opacity: 1; + } + + .documentDecorations-bottomRightResizer { + grid-row: 4; + } + + .documentDecorations-topRightResizer, + .documentDecorations-bottomLeftResizer { + cursor: nesw-resize; + background: unset; + opacity: 1; + } - .documentDecorations-topRightResizer { - border-right: 2px solid; - border-top: 2px solid; - } + .documentDecorations-topRightResizer { + border-right: 2px solid; + border-top: 2px solid; + } - .documentDecorations-bottomLeftResizer { - border-left: 2px solid; - border-bottom: 2px solid; - } + .documentDecorations-bottomLeftResizer { + border-left: 2px solid; + border-bottom: 2px solid; + } - .documentDecorations-topRightResizer:hover, - .documentDecorations-bottomLeftResizer:hover { +.documentDecorations-topRightResizer:hover, +.documentDecorations-bottomLeftResizer:hover { cursor: nesw-resize; background: black; opacity: 1; - } +} - .documentDecorations-topResizer, - .documentDecorations-bottomResizer { +.documentDecorations-topResizer, +.documentDecorations-bottomResizer { cursor: ns-resize; - } +} - .documentDecorations-title-Dark, - .documentDecorations-title { +.documentDecorations-title-Dark, +.documentDecorations-title { opacity: 1; - grid-column-start: 2; - grid-column-end: 4; + width: calc(100% - 8px); // = margin-left + margin-right + grid-column: 2; + grid-column-end: 2; pointer-events: auto; overflow: hidden; text-align: center; display: flex; - margin-left: 5px; + margin-left: 6px; // closeButton width (14) - leftColumn width (8) + margin-right: 2px; height: 20px; position: absolute; border-radius: 8px; @@ -247,7 +246,7 @@ $linkGap: 3px; .documentDecorations-titleSpan, .documentDecorations-titleSpan-Dark { - width: 100%; + width: 100% ; border-radius: 8px; background: #ffffffa0; position: absolute; @@ -263,105 +262,62 @@ $linkGap: 3px; background: black; } - .documentDecorations-contextMenu { - width: 25px; - height: calc(100% + 8px); // 8px for the height of the top resizer bar - grid-column-start: 2; - grid-column-end: 2; - pointer-events: all; - padding-left: 5px; - cursor: pointer; - } - - .documentDecorations-titleBackground { - background: #ffffffcf; - border-radius: 8px; - width: 100%; - height: 100%; - position: absolute; - } - - .documentDecorations-title { - opacity: 1; - grid-column-start: 2; - grid-column-end: 4; - pointer-events: auto; - overflow: hidden; - text-align: center; - display: flex; - margin-left: 5px; - height: 20px; - position: absolute; - .documentDecorations-titleSpan { - width: 100%; - border-radius: 8px; - background: #ffffffcf; - position: absolute; - display: inline-block; - cursor: move; - } - } - - .focus-visible { - margin-left: 0px; - } -} + .documentDecorations-titleBackground { + background: #ffffffcf; + border-radius: 8px; + width: 100%; + height: 100%; + position: absolute; + } -.documentDecorations-iconifyButton { - opacity: 1; - grid-column-start: 4; - grid-column-end: 4; - pointer-events: all; - right: 0; - cursor: pointer; - position: absolute; - width: 20px; + .focus-visible { + margin-left: 0px; + } } .documentDecorations-openButton { - display: flex; - align-items: center; - opacity: 1; - grid-column-start: 5; - grid-column-end: 5; - pointer-events: all; - cursor: pointer; + display: flex; + align-items: center; + opacity: 1; + grid-column-start: 3; + pointer-events: all; + cursor: pointer; } .documentDecorations-closeButton { - display: flex; - align-items: center; - opacity: 1; - grid-column-start: 1; - grid-column-end: 3; - pointer-events: all; - cursor: pointer; - - > svg { - margin: 0; - } + display: flex; + align-items: center; + opacity: 1; + grid-column: 1; + pointer-events: all; + width: 14px; + cursor: pointer; + + > svg { + margin: 0; + } } .documentDecorations-background { - background: lightblue; - position: absolute; - opacity: 0.1; + background: lightblue; + position: absolute; + opacity: 0.1; } .linkFlyout { - grid-column: 2/4; + grid-column: 2/4; } .linkButton-empty:hover { - background: $medium-gray; - transform: scale(1.05); - cursor: pointer; + background: $medium-gray; + transform: scale(1.05); + cursor: pointer; } .linkButton-nonempty:hover { - background: $medium-gray; - transform: scale(1.05); - cursor: pointer; + background: $medium-gray; + transform: scale(1.05); + cursor: pointer; } .link-button-container { @@ -379,132 +335,132 @@ $linkGap: 3px; } .linkButtonWrapper { - pointer-events: auto; - padding-right: 5px; - width: 25px; + pointer-events: auto; + padding-right: 5px; + width: 25px; } .linkButton-linker { - height: 20px; - width: 20px; - text-align: center; - border-radius: 50%; - pointer-events: auto; - color: $dark-gray; - border: $dark-gray 1px solid; + height: 20px; + width: 20px; + text-align: center; + border-radius: 50%; + pointer-events: auto; + color: $dark-gray; + border: $dark-gray 1px solid; } .linkButton-linker:hover { - cursor: pointer; - transform: scale(1.05); + cursor: pointer; + transform: scale(1.05); } .linkButton-empty, .linkButton-nonempty { - height: 20px; - width: 20px; - border-radius: 50%; - opacity: 0.9; - pointer-events: auto; - background-color: $dark-gray; - color: $white; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - transition: transform 0.2s; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - - &:hover { - background: $medium-gray; - transform: scale(1.05); - cursor: pointer; - } + height: 20px; + width: 20px; + border-radius: 50%; + opacity: 0.9; + pointer-events: auto; + background-color: $dark-gray; + color: $white; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background: $medium-gray; + transform: scale(1.05); + cursor: pointer; + } } .templating-menu { - position: absolute; - pointer-events: auto; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - transition: transform 0.2s; - text-align: center; - display: flex; - justify-content: center; - align-items: center; + position: absolute; + pointer-events: auto; + text-transform: uppercase; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + text-align: center; + display: flex; + justify-content: center; + align-items: center; } .documentdecorations-icon { - margin: 0px; + margin: 0px; } .templating-button, .docDecs-tagButton { - width: 20px; - height: 20px; - border-radius: 50%; - opacity: 0.9; - font-size: 14; - background-color: $dark-gray; - color: $white; - text-align: center; - cursor: pointer; - - &:hover { - background: $medium-gray; - transform: scale(1.05); - } + width: 20px; + height: 20px; + border-radius: 50%; + opacity: 0.9; + font-size: 14; + background-color: $dark-gray; + color: $white; + text-align: center; + cursor: pointer; + + &:hover { + background: $medium-gray; + transform: scale(1.05); + } } #template-list { - position: absolute; - top: 25px; - left: 0px; - width: max-content; - font-family: $sans-serif; - font-size: 12px; - background-color: $light-gray; - padding: 2px 12px; - list-style: none; - - .templateToggle, - .chromeToggle { - text-align: left; - } - - input { - margin-right: 10px; - } + position: absolute; + top: 25px; + left: 0px; + width: max-content; + font-family: $sans-serif; + font-size: 12px; + background-color: $light-gray; + padding: 2px 12px; + list-style: none; + + .templateToggle, + .chromeToggle { + text-align: left; + } + + input { + margin-right: 10px; + } } @-moz-keyframes spin { - 100% { - -moz-transform: rotate(360deg); - } + 100% { + -moz-transform: rotate(360deg); + } } @-webkit-keyframes spin { - 100% { - -webkit-transform: rotate(360deg); - } + 100% { + -webkit-transform: rotate(360deg); + } } @keyframes spin { - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } } @keyframes shadow-pulse { - 0% { - box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8); - } + 0% { + box-shadow: 0 0 0 0px rgba(0, 0, 0, 0.8); + } - 100% { - box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); - } + 100% { + box-shadow: 0 0 0 10px rgba(0, 255, 0, 0); + } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d7ead713a..062ca1181 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -144,10 +144,19 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return true; } + _iconifyTimeout: NodeJS.Timeout | undefined; onCloseClick = () => { - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + if (this.canDelete) { + const views = SelectionManager.Views().slice().filter(v => v); + const icons = this._iconifyTimeout ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon"); + const others = this._iconifyTimeout ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon"); + icons.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); + others.forEach(dv => dv.iconify()); + others.length && (this._iconifyTimeout = setTimeout(() => { + views.forEach(view => SelectionManager.DeselectView(view)); + this._iconifyTimeout = undefined; + }, 1000)); + } } onMaximizeDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, () => { @@ -171,7 +180,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P } else if (e.altKey) { // open same document in new tab CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right"); } else { - LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document)); + var openDoc = selectedDocs[0].props.Document; + if (openDoc.layoutKey === "layout_icon") { + openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); + Doc.deiconifyView(openDoc); + } + LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document)); } } SelectionManager.DeselectAll(); @@ -431,20 +445,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return SelectionManager.Views().length > 1 ? "-multiple-" : "-unset-"; } + @computed get canDelete() { + return SelectionManager.Views().some(docView => { + if (docView.rootDoc.stayInCollection) return false; + const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; + //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) && + return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin); + }); + } + render() { const bounds = this.Bounds; const seldoc = SelectionManager.Views().slice(-1)[0]; if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } - const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles; + const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc.isGroup; const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle; const canOpen = SelectionManager.Views().some(docView => !docView.props.Document._stayInCollection && !docView.props.Document.isGroup && !docView.props.Document.hideOpenButton); - const canDelete = SelectionManager.Views().some(docView => { - const collectionAcl = docView.props.ContainingCollectionView ? GetEffectiveAcl(docView.props.ContainingCollectionDoc?.[DataSym]) : AclEdit; - //return (!docView.rootDoc._stayInCollection || docView.rootDoc.isInkMask) && - return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin); - }); + const canDelete = this.canDelete; const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => ( <Tooltip key={key} title={<div className="dash-tooltip">{title}</div>} placement="top"> <div className={`documentDecorations-${key}Button`} onContextMenu={e => e.preventDefault()} @@ -454,16 +473,15 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P </Tooltip>); const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); - const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} style={{ width: "100%" }} key="title" /> : + const titleArea = hideTitle ? <div className="documentDecorations-title" onPointerDown={this.onTitleDown} key="title" /> : this._edtingTitle ? <input ref={this._keyinput} className={`documentDecorations-title${colorScheme}`} - style={{ width: `calc(100% - ${hideResizers ? 0 : 20}px` }} type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle} onBlur={e => this.titleBlur()} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> : - <div className="documentDecorations-title" style={{ width: `calc(100% - ${hideResizers ? 0 : 20}px` }} key="title" onPointerDown={this.onTitleDown} > + <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} > <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span> </div>; @@ -503,8 +521,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} {hideResizers ? (null) : <> - {SelectionManager.Views().length !== 1 || hideTitle ? (null) : - topBtn("iconify", `window-${seldoc.finalLayoutKey.includes("icon") ? "restore" : "minimize"}`, undefined, this.onIconifyClick, `${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`)} <div key="tl" className={`documentDecorations-topLeftResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> <div key="t" className={`documentDecorations-topResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> <div key="tr" className={`documentDecorations-topRightResizer ${resizerScheme}`} onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()} /> diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 4a327a842..91e0503b3 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -142,11 +142,18 @@ export class KeyManager { case "delete": case "backspace": if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") { - const selected = SelectionManager.Views().filter(dv => !dv.topMost); UndoManager.RunInBatch(() => { - SelectionManager.DeselectAll(); - selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document)); - }, "delete"); + if (LightboxView.LightboxDoc) { + LightboxView.SetLightboxDoc(undefined); + SelectionManager.DeselectAll(); + } + else DocumentDecorations.Instance.onCloseClick(); + }, "backspace"); + // const selected = SelectionManager.Views().filter(dv => !dv.topMost); + // UndoManager.RunInBatch(() => { + // SelectionManager.DeselectAll(); + // selected.map(dv => !dv.props.Document._stayInCollection && dv.props.removeDocument?.(dv.props.Document)); + // }, "delete"); return { stopPropagation: true, preventDefault: true }; } break; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 649ee8394..9803f9782 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -98,7 +98,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc && StrCast(doc._showTitle, props?.showTitle?.() || - (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ? + (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any) ? (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "")) || ""); case StyleProp.Color: @@ -114,10 +114,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case StyleProp.BorderPath: return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 }; case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || - doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; + doc?.type === DocumentType.RTF || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY; - let docColor: Opt<string> = StrCast(doc?.[fieldKey + "backgroundColor"], StrCast(doc?._backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : "")); + let docColor: Opt<string> = + StrCast(doc?.[fieldKey + "backgroundColor"], + StrCast(doc?._backgroundColor, + StrCast(props?.Document.backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : ""))); switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break; case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; @@ -127,7 +130,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break; case DocumentType.SLIDER: break; case DocumentType.EQUATION: docColor = docColor || "transparent"; break; - case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break; + case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined) || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; case DocumentType.LINKANCHOR: docColor = isAnchor ? Colors.LIGHT_BLUE : "transparent"; break; case DocumentType.LINK: docColor = (isAnchor ? docColor : "") || "transparent"; break; @@ -140,13 +143,16 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case DocumentType.COL: if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break; docColor = docColor || - (doc?._isGroup ? "#00000004" : // very faint highlight to show bounds of group - (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background - isBackground() ? "cyan" : // ?? is there a good default for a background collection - doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc + (doc?._viewType === CollectionViewType.Pile || Doc.IsSystem(doc) ? (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY) : // system docs (seen in treeView) get a grayish background + isBackground() ? "cyan" : // ?? is there a good default for a background collection + doc.annotationOn ? "#00000015" : // faint interior for collections on PDFs, images, etc + (doc?._isGroup ? undefined : StrCast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : - Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? Colors.BLACK : Colors.WHITE)))); + Doc.UserDoc().activeCollectionBackground ?? (darkScheme() ? + Colors.BLACK : + "linear-gradient(#065fff, #85c1f9)")) + )); break; //if (doc._viewType !== CollectionViewType.Freeform && doc._viewType !== CollectionViewType.Time) return "rgb(62,62,62)"; default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 467c2893f..abb4b6bc6 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -44,14 +44,14 @@ export class CollectionCarouselView extends CollectionSubView() { @computed get content() { const index = NumCast(this.layoutDoc._itemIndex); const curDoc = this.childLayoutPairs?.[index]; - const captionProps = { ...this.props, fieldKey: "caption" }; + const captionProps = { ...OmitKeys(this.props, ["setHeight",]).omit, fieldKey: "caption" }; const marginX = NumCast(this.layoutDoc["caption-xMargin"]); const marginY = NumCast(this.layoutDoc["caption-yMargin"]); const showCaptions = StrCast(this.layoutDoc._showCaption); return !(curDoc?.layout instanceof Doc) ? (null) : <> <div className="collectionCarouselView-image" key="image"> - <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit} + <DocumentView {...OmitKeys(this.props, ["setHeight", "NativeWidth", "NativeHeight", "childLayoutTemplate", "childLayoutString"]).omit} onDoubleClick={this.onContentDoubleClick} onClick={this.onContentClick} hideCaptions={showCaptions ? true : false} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 6931d9896..e95371aaa 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -92,7 +92,7 @@ export class CollectionDockingView extends CollectionSubView() { DragManager.CompleteWindowDrag = undefined; } finishDrag?.(aborted); - } + }; } @undoBatch public CloseFullScreen = () => { @@ -316,7 +316,7 @@ export class CollectionDockingView extends CollectionSubView() { } this._goldenLayout.init(); this._goldenLayout.root.layoutManager.on('itemDropped', this.tabItemDropped); - this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart) + this._goldenLayout.root.layoutManager.on('dragStart', this.tabDragStart); this._goldenLayout.root.layoutManager.on('activeContentItemChanged', this.stateChanged); } } diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 0ca0a463e..3d5552f81 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -11,6 +11,7 @@ import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormV import "./CollectionPileView.scss"; import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); +import { ScriptField } from "../../../fields/ScriptField"; @observer export class CollectionPileView extends CollectionSubView() { @@ -47,6 +48,10 @@ export class CollectionPileView extends CollectionSubView() { return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; } + toggleIcon = () => { + return ScriptField.MakeScript("documentView.iconify()", { documentView: "any" }); + } + // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { const isStarburst = this.layoutEngine() === "starburst"; @@ -60,6 +65,7 @@ export class CollectionPileView extends CollectionSubView() { layoutEngine={this.layoutEngine} childDocumentsActive={isStarburst ? returnTrue : undefined} addDocument={this.addPileDoc} + childClickScript={this.toggleIcon()} moveDocument={this.removePileDoc} /> </div>; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 9fed82dae..f9345e33f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -113,16 +113,17 @@ export function computerStarburstLayout( ) { const docMap = new Map<string, PoolData>(); const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])]; - const docScale = NumCast(pivotDoc._starburstDocScale); - const docSize = docScale * 100; // assume a icon sized at 100 + const docSize = 75; // assume a icon sized at 100 const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize]; childPairs.forEach(({ layout, data }, i) => { + const docSize = layout.layoutKey === "layout_icon" ? 75 : 400; // assume a icon sized at 100 const deg = i / childPairs.length * Math.PI * 2; docMap.set(layout[Id], { - x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2, - y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2, - width: docScale * layout[WidthSym](), - height: docScale * layout[HeightSym](), + x: Math.cos(deg) * (burstRadius[0] / 3) - docSize / 2, + y: Math.sin(deg) * (burstRadius[1] / 3) - docSize * layout[HeightSym]() / layout[WidthSym]() / 2, + width: docSize,//layout[WidthSym](), + height: docSize * layout[HeightSym]() / layout[WidthSym](), + zIndex: NumCast(layout.zIndex), pair: { layout, data }, replica: "" }); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b2697cf08..9b0acffdf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,6 +9,7 @@ import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fiel import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; +import { ImageField } from "../../../../fields/URLField"; import { createSchema, listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; @@ -41,6 +42,8 @@ import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDo import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; +import { VideoBox } from "../../nodes/VideoBox"; +import { CreateImage } from "../../nodes/WebBoxRenderer"; import { StyleLayers, StyleProp } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; @@ -1011,8 +1014,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } else if (doc.isInkMask) { doc.zIndex = 5000; } else { - const docs = this.childLayoutPairs.map(pair => pair.layout); - docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); + docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1; if (zlast - docs.length > 100) { for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; @@ -1411,6 +1414,37 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection })); } + updateIcon = () => { + this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); + this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); + const htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); + this.props.docViewPath().lastElement().ContentDiv!.style.width = ""; + this.props.docViewPath().lastElement().ContentDiv!.style.height = ""; + const nativeWidth = this.layoutDoc[WidthSym](); + const nativeHeight = this.layoutDoc[HeightSym](); + + CreateImage( + "", + document.styleSheets, + htmlString?.replace(/docView-hack/g, 'documentView-hack'), + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) + ).then + ((data_url: any) => { + VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then( + returnedfilename => setTimeout(action(() => { + + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); @@ -1476,6 +1510,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 62510ce9d..41e4d6b6a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -10,6 +10,7 @@ user-select: none; } + .marqueeView:focus-within { overflow: hidden; } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 5149d370f..3e35039e3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -18,6 +18,8 @@ import "./CollectionFreeFormDocumentView.scss"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); import { DocumentType } from "../../documents/DocumentTypes"; +import { collectionSchema } from "../../../fields/documentSchemas"; +import { CollectionViewType } from "../collections/CollectionView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; @@ -167,7 +169,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF PanelHeight: this.panelHeight, }; const background = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && DashColor(background).alpha() !== 1 ? "multiply" : undefined); + const mixBlendMode = StrCast(this.layoutDoc.mixBlendMode) as any || (typeof background === "string" && background && (!background.startsWith("linear") && DashColor(background).alpha() !== 1) ? "multiply" : undefined); return <div className={"collectionFreeFormDocumentView-container"} style={{ outline: this.Highlight ? "orange solid 2px" : "", @@ -176,7 +178,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF transform: this.transform, transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition), zIndex: this.ZInd, - mixBlendMode, + mixBlendMode: mixBlendMode, display: this.ZInd === -99 ? "none" : undefined }} > {this.props.renderCutoffProvider(this.props.Document) ? diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx index 433a0bf48..a9c998757 100644 --- a/src/client/views/nodes/DocumentIcon.tsx +++ b/src/client/views/nodes/DocumentIcon.tsx @@ -1,3 +1,4 @@ + import { observer } from "mobx-react"; import * as React from "react"; import { DocumentView } from "./DocumentView"; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 6d96a9ab6..46537df7e 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -278,7 +278,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp //render circular tooltip if it isn't set to invisible and show the number of doc links the node has, and render inner-menu link button for starting/stopping links if currently in menu return (!Array.from(this.filteredLinks).length && !this.props.AlwaysOn) ? (null) : - <div className="documentLinksButton-wrapper" > + <div className="documentLinksButton-wrapper" style={{ transform: this.props.InMenu ? undefined : `scale(${(this.props.ContentScaling?.() || 1) * this.props.View.screenToLocalTransform().Scale})` }} > { (this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink)) || (!DocumentLinksButton.LinkEditorDocView && !this.props.InMenu) ? diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 4565f8504..5c5d2df10 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -3,6 +3,9 @@ .documentView-effectsWrapper { border-radius: inherit; } +.documentView-hack { + display: inline; +} .documentView-customBorder { width:100%; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 103c3624d..195f6c9e5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -80,6 +80,7 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment> export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; export interface DocComponentView { + updateIcon?: () => void; // updates the icon representation of the document getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document @@ -751,7 +752,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps zorderItems.push({ description: "Send to Back", event: () => SelectionManager.Views().forEach(dv => dv.props.bringToFront(dv.rootDoc, true)), icon: "expand-arrows-alt" }); zorderItems.push({ description: this.rootDoc._raiseWhenDragged !== false ? "Keep ZIndex when dragged" : "Allow ZIndex to change when dragged", event: undoBatch(action(() => this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined)), icon: "expand-arrows-alt" }); } - !zorders && cm.addItem({ description: "ZOrder...", subitems: zorderItems, icon: "compass" }); + !zorders && cm.addItem({ description: "ZOrder...", noexpand: true, subitems: zorderItems, icon: "compass" }); !Doc.UserDoc().noviceMode && onClicks.push({ description: "Enter Portal", event: this.makeIntoPortal, icon: "window-restore" }); !Doc.UserDoc().noviceMode && onClicks.push({ description: "Toggle Detail", event: this.setToggleDetail, icon: "concierge-bell" }); @@ -858,9 +859,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }} icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" /> </div>; + return <div className="documentView-contentsView" style={{ - pointerEvents: this.props.pointerEvents as any ? this.props.pointerEvents as any : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"), + pointerEvents: this.props.pointerEvents as any ?? this.rootDoc.layoutKey === "layout_icon" ? "none" : (this.rootDoc.type !== DocumentType.INK && ((this.props.contentPointerEvents as any) || (this.isContentActive())) ? "all" : "none"), height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> {!this._retryThumb || !this.thumbShown() ? (null) : @@ -886,10 +888,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps focus={this.focus} layoutKey={this.finalLayoutKey} /> {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints} - {!this.props.isSelected() || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : + {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) : <DocumentLinksButton View={this.props.DocumentView()} ContentScaling={this.props.ContentScaling} - Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} /> + Offset={[this.topMost ? 0 : !this.props.isSelected() ? - 15 : -30, undefined, undefined, this.topMost ? 10 : !this.props.isSelected() ? - 15 : -30]} /> } {audioView} </div>; @@ -1029,7 +1031,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps /> </div>; const targetDoc = (showTitle?.startsWith("_") ? this.layoutDoc : this.rootDoc); - const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"); + const background = StrCast(SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, + Doc.UserDoc().showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"); const titleView = !showTitle ? (null) : <div className={`documentView-titleWrapper${showTitleHover ? "-hover" : ""}`} key="title" style={{ position: this.headerMargin ? "relative" : "absolute", @@ -1110,7 +1113,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this.boxShadow || (this.props.Document.isTemplateForField ? "black 0.2vw 0.2vw 0.8vw" : undefined); // Return surrounding highlight - return <div className={DocumentView.ROOT_DIV} ref={this._mainCont} + return <div className={`${DocumentView.ROOT_DIV} docView-hack`} ref={this._mainCont} onContextMenu={this.onContextMenu} onKeyDown={this.onKeyDown} onPointerDown={this.onPointerDown} @@ -1118,6 +1121,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onPointerEnter={e => !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)} onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)} style={{ + display: this.hidden ? "inline" : undefined, borderRadius: this.borderRounding, pointerEvents: this.pointerEvents, outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", @@ -1233,6 +1237,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { } public iconify() { + this.ComponentView?.updateIcon?.(); const layoutKey = Cast(this.Document.layoutKey, "string", null); if (layoutKey !== "layout_icon") { this.switchViews(true, "icon"); @@ -1241,6 +1246,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { const deiconifyLayout = Cast(this.Document.deiconifyLayout, "string", null); this.switchViews(deiconifyLayout ? true : false, deiconifyLayout); this.Document.deiconifyLayout = undefined; + this.props.bringToFront(this.rootDoc); } } @undoBatch @@ -1319,7 +1325,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { ContentScaling={this.ContentScale} ScreenToLocalTransform={this.screenToLocalTransform} focus={this.props.focus || emptyFunction} - bringToFront={emptyFunction} ref={action((r: DocumentViewInternal | null) => r && (this.docView = r))} /> </div>)} </div>); @@ -1332,8 +1337,8 @@ export function deiconifyViewFunc(documentView: DocumentView) { } ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.iconify(); -} -); + documentView.select(false); +}); ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 17f95c1cc..fb9e86487 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -73,7 +73,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0], selected: this.props.isSelected() }), - ({ forceFull, scrSize, selected }) => this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", + ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", { fireImmediately: true, delay: 1000 }); this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { @@ -245,8 +245,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @computed get nativeSize() { TraceMobx(); - const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 500); - const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1); + const nativeWidth = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"], 500)); + const nativeHeight = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], NumCast(this.layoutDoc[this.fieldKey + "-nativeHeight"], 1)); const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + "-nativeOrientation"], 1); return { nativeWidth, nativeHeight, nativeOrientation }; } diff --git a/src/client/views/nodes/LabelBigText.js b/src/client/views/nodes/LabelBigText.js index db43551d8..02c36c4bc 100644 --- a/src/client/views/nodes/LabelBigText.js +++ b/src/client/views/nodes/LabelBigText.js @@ -5,14 +5,14 @@ And from Jetroid/bigtext.js v1.0.0, September 2016 Usage: BigText("#myElement",{ - rotateText: {Number}, (null) - fontSizeFactor: {Number}, (0.8) - maximumFontSize: {Number}, (null) - limitingDimension: {String}, ("both") - horizontalAlign: {String}, ("center") - verticalAlign: {String}, ("center") - textAlign: {String}, ("center") - whiteSpace: {String}, ("nowrap") + rotateText: {Number}, (null) + fontSizeFactor: {Number}, (0.8) + maximumFontSize: {Number}, (null) + limitingDimension: {String}, ("both") + horizontalAlign: {String}, ("center") + verticalAlign: {String}, ("center") + textAlign: {String}, ("center") + whiteSpace: {String}, ("nowrap") }); @@ -28,6 +28,8 @@ fontSizeFactor: This option is used to give some vertical spacing for letters th maximumFontSize: maximum font size to use. +minimumFontSize: minimum font size to use. if font is calculated smaller than this, text will be rendered at this size and wrapped + limitingDimension: In which dimension the font size should be limited. Possible values: "width", "height" or "both". Defaults to both. Using this option with values different than "both" overwrites the element parent width or height. horizontalAlign: Where to align the text horizontally. Possible values: "left", "center", "right". Defaults to "center". @@ -154,6 +156,7 @@ export default function BigText(element, options) { width: element.offsetWidth, //Note: This is slightly larger than the jQuery version height: element.offsetHeight, }; + if (!box.width || !box.height) return element; if (options.rotateText !== null) { @@ -187,7 +190,20 @@ export default function BigText(element, options) { lineHeight = Math.floor(heightFactor * 1000); var fontSize = lineHeight * options.fontSizeFactor; - if (options.maximumFontSize !== null && fontSize > options.maximumFontSize) { + if (fontSize < options.minimumFontSize) { + parentStyle.display = "flex"; + parentStyle.alignItems = "center"; + style.whiteSpace = "pre-wrap"; + style.textAlign = "center"; + style.visibility = ""; + style.fontSize = "18px"; + style.lineHeight = "20px"; + style.top = ""; + style.left = ""; + style.margin = ""; + return element; + } + if (options.maximumFontSize && fontSize > options.maximumFontSize) { fontSize = options.maximumFontSize; lineHeight = fontSize / options.fontSizeFactor; } diff --git a/src/client/views/nodes/LabelBox.scss b/src/client/views/nodes/LabelBox.scss index 6a0d651d2..42e158584 100644 --- a/src/client/views/nodes/LabelBox.scss +++ b/src/client/views/nodes/LabelBox.scss @@ -4,7 +4,7 @@ border-radius: inherit; display: flex; flex-direction: column; - position: absolute; + position: relative; } .labelBox-mainButton { diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 0015f0b71..d539ca9b8 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { Cast, StrCast } from '../../../fields/Types'; +import { Cast, StrCast, NumCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; @@ -73,6 +73,19 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro @observable _mouseOver = false; @computed get hoverColor() { return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : "unset"; } + fitTextToBox = (r: any) => { + BigText(r, { + rotateText: null, + fontSizeFactor: 1, + minimumFontSize: NumCast(this.layoutDoc._minFontSize), + maximumFontSize: NumCast(this.layoutDoc._maxFontSize), + limitingDimension: "both", + horizontalAlign: "center", + verticalAlign: "center", + textAlign: "center", + whiteSpace: "nowrap" + }); + } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); @@ -97,16 +110,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro }} > <span ref={r => { if (r) { - BigText(r, { - rotateText: null, - fontSizeFactor: 1, - maximumFontSize: null, - limitingDimension: "both", - horizontalAlign: "center", - verticalAlign: "center", - textAlign: "center", - whiteSpace: "nowrap" - }); + if (!r.offsetWidth || !r.offsetHeight) setTimeout(() => this.fitTextToBox(r)); + else this.fitTextToBox(r); } }}>{label.startsWith("#") ? (null) : label}</span> </div> diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index a2e7d2aa3..c73823a2b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -2,10 +2,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; +import { CreateImage } from "../nodes/WebBoxRenderer"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Cast, NumCast, StrCast, ImageCast } from '../../../fields/Types'; -import { PdfField } from "../../../fields/URLField"; +import { ImageField, PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; @@ -21,6 +22,8 @@ import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; import "./PDFBox.scss"; import React = require("react"); +import { Id } from '../../../fields/FieldSymbols'; +import { VideoBox } from './VideoBox'; @observer export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { @@ -52,6 +55,56 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } + replaceCanvases = (oldDiv: HTMLElement, newDiv: HTMLElement) => { + if (oldDiv.childNodes) { + for (let i = 0; i < oldDiv.childNodes.length; i++) { + this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); + } + } + if (oldDiv instanceof HTMLCanvasElement) { + const canvas = oldDiv as HTMLCanvasElement; + const img = document.createElement('img'); // create a Image Element + img.src = canvas.toDataURL(); //image source + img.style.width = canvas.style.width; + img.style.height = canvas.style.height; + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + } + + updateIcon = () => { + const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; + newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); + newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + this.replaceCanvases(docViewContent, newDiv); + const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); + const nativeWidth = this.layoutDoc[WidthSym](); + const nativeHeight = this.layoutDoc[HeightSym](); + + CreateImage( + "", + document.styleSheets, + htmlString?.replace(/docView-hack/g, 'documentView-hack'), + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) * this.props.PanelHeight() / NumCast(this.rootDoc[this.fieldKey + "-nativeHeight"]) + ).then + ((data_url: any) => { + VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then( + returnedfilename => setTimeout(action(() => { + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + componentWillUnmount() { this._selectReactionDisposer?.(); } componentDidMount() { this.props.setContentView?.(this); diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 3cf10a033..f47a71469 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -81,6 +81,7 @@ align-items: center; justify-content: center; display: flex; + width: 100%; visibility: none; opacity: 0; background-color: $dark-gray; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ca8dc8515..c31d38b0d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,10 +4,10 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from "mobx-react"; import { basename } from "path"; import * as rp from 'request-promise'; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, WidthSym } from "../../../fields/Doc"; import { InkTool } from "../../../fields/InkField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { AudioField, VideoField } from "../../../fields/URLField"; +import { AudioField, ImageField, VideoField } from "../../../fields/URLField"; import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; @@ -220,16 +220,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // creates and links snapshot photo of current video frame - @action public Snapshot(downX?: number, downY?: number) { + @action public Snapshot = (downX?: number, downY?: number, cb?: (filename: string, x: number | undefined, y: number | undefined) => void) => { const width = NumCast(this.layoutDoc._width); const canvas = document.createElement('canvas'); canvas.width = 640; canvas.height = 640 * Doc.NativeHeight(this.layoutDoc) / (Doc.NativeWidth(this.layoutDoc) || 1); const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { - // ctx.rect(0, 0, canvas.width, canvas.height); - // ctx.fillStyle = "blue"; - // ctx.fill(); this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height); } @@ -259,10 +256,19 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp const encodedFilename = encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_")); const filename = basename(encodedFilename); VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) => - returnedFilename && this.createRealSummaryLink(returnedFilename, downX, downY)); + returnedFilename && (cb ?? this.createRealSummaryLink)(returnedFilename, downX, downY)); } } + updateIcon = () => { + const makeIcon = (returnedfilename: string) => { + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = this.layoutDoc[WidthSym](); + this.dataDoc["icon-nativeHeight"] = this.layoutDoc[HeightSym](); + }; + this.Snapshot(undefined, undefined, makeIcon); + } + // creates link for snapshot createRealSummaryLink = (imagePath: string, downX?: number, downY?: number) => { const url = !imagePath.startsWith("/") ? Utils.CorsProxy(imagePath) : imagePath; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index c12059943..0fd193977 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -130,8 +130,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.setScrollPos(this._initialScroll); } })); - } else if (!this.props.isContentActive() && - !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && /// don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty + } else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) + !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. const imageBitmap = ImageCast(this.layoutDoc["thumb-frozen"])?.url.href; if (this._iframe && !imageBitmap) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ec88b8d1d..b6713d878 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -364,7 +364,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } updateTitle = () => { - const title = StrCast(this.dataDoc.title) + const title = StrCast(this.dataDoc.title); if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 651b0401b..9aaa6e90f 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -74,7 +74,7 @@ export class PDFViewer extends React.Component<IViewerProps> { private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); + _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _selectionText: string = ""; private _downX: number = 0; private _downY: number = 0; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ebdbae344..15df673f3 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1119,7 +1119,7 @@ export namespace Doc { return fieldStr.includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - export function deiconifyView(doc: any) { + export function deiconifyView(doc: Doc) { StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); } |