aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
authorAubrey Li <Aubrey-Li>2022-04-14 17:43:09 -0400
committerAubrey Li <Aubrey-Li>2022-04-14 17:43:09 -0400
commitcf379920876cafa84b0f342dbd981d60a66b6b4a (patch)
tree3059e2cbc4028abe0b308c5a0fc59744b60faeb5 /src/client/views/nodes
parent98567999b9882ed093bb0d750504a1bd10aa9a94 (diff)
parent9ec7503370229c17cf8a0d3cf77571010db04a9b (diff)
Merge remote-tracking branch 'origin/presentation_group_test' into presentation_upgrade
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.tsx8
-rw-r--r--src/client/views/nodes/ImageBox.scss16
-rw-r--r--src/client/views/nodes/ImageBox.tsx18
-rw-r--r--src/client/views/nodes/LinkDocPreview.tsx24
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx28
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts19
-rw-r--r--src/client/views/nodes/formattedText/marks_rts.ts13
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx1
-rw-r--r--src/client/views/nodes/trails/PresElementBox.scss78
-rw-r--r--src/client/views/nodes/trails/PresElementBox.tsx86
11 files changed, 250 insertions, 44 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 6f0f0074a..5149d370f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -46,6 +46,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
get ZInd() { return this.dataProvider ? this.dataProvider.zIndex : NumCast(this.Document.zIndex); }
get Opacity() { return this.dataProvider ? this.dataProvider.opacity : undefined; }
get Highlight() { return this.dataProvider?.highlight; }
+ @computed get ShowTitle() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as (Opt<string>); }
@computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); }
@computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); }
@@ -171,7 +172,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
style={{
outline: this.Highlight ? "orange solid 2px" : "",
width: this.panelWidth(),
- height: this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
+ height: !this.ShowTitle && this.layoutDoc.autoHeight && this.rootDoc.type === DocumentType.RTF ? undefined : this.panelHeight(),
transform: this.transform,
transition: this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition),
zIndex: this.ZInd,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c7f7cdb58..7361dba73 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -111,6 +111,7 @@ export interface DocumentViewSharedProps {
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
thumbShown?: () => boolean;
+ isHovering?: () => boolean;
setContentView?: (view: DocComponentView) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
@@ -873,6 +874,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
{...this.props}
docViewPath={this.props.viewPath}
thumbShown={this.thumbShown}
+ isHovering={this.isHovering}
setContentView={this.setContentView}
scaling={this.contentScaling}
PanelHeight={this.panelHeight}
@@ -884,7 +886,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
focus={this.focus}
layoutKey={this.finalLayoutKey} />
{this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
- {this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? (null) :
+ {!this.props.isSelected() || 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]} />
@@ -1065,6 +1067,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
{captionView}
</div>;
}
+ isHovering = () => this._isHovering;
+ @observable _isHovering = false;
@observable _: string = "";
@computed get renderDoc() {
TraceMobx();
@@ -1074,6 +1078,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
return this.docContents ??
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
id={this.props.Document[Id]}
+ onPointerEnter={action(() => this._isHovering = true)}
+ onPointerLeave={action(() => this._isHovering = false)}
style={{
background: isButton || thumb ? undefined : this.backgroundColor,
opacity: this.opacity,
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 4238f6d29..cd2b23f02 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -103,7 +103,7 @@
display: flex;
height: 100%;
- .imageBox-fadeBlocker {
+ .imageBox-fadeBlocker, .imageBox-fadeBlocker-hover{
width: 100%;
height: 100%;
position: absolute;
@@ -133,12 +133,10 @@
transition: opacity 1s ease-in-out;
}
-.imageBox:hover {
- .imageBox-fadeBlocker {
- -webkit-transition: opacity 1s ease-in-out;
- -moz-transition: opacity 1s ease-in-out;
- -o-transition: opacity 1s ease-in-out;
- transition: opacity 1s ease-in-out;
- opacity: 0;
- }
+.imageBox-fadeBlocker-hover {
+ -webkit-transition: opacity 1s ease-in-out;
+ -moz-transition: opacity 1s ease-in-out;
+ -o-transition: opacity 1s ease-in-out;
+ transition: opacity 1s ease-in-out;
+ opacity: 0;
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 0a4168698..17f95c1cc 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -45,7 +45,6 @@ const uploadIcons = {
export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() {
protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined;
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
- private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _dropDisposer?: DragManager.DragDropDisposer;
private _disposers: { [name: string]: IReactionDisposer } = {};
@observable _curSuffix = "";
@@ -78,7 +77,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
{ fireImmediately: true, delay: 1000 });
this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }),
({ nativeSize, width }) => {
- if (!this.layoutDoc._height) {
+ if (true || !this.layoutDoc._height) {
this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth;
}
},
@@ -282,17 +281,18 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
return <div className="imageBox-cont" key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}>
<div className="imageBox-fader" style={{ overflow: Array.from(this.props.docViewPath?.()).slice(-1)[0].fitWidth ? "auto" : undefined }} >
- <img key="paths" ref={this._imgRef}
+ <img key="paths"
src={srcpath}
style={{ transform, transformOrigin }}
draggable={false}
width={nativeWidth} />
- {fadepath === srcpath ? (null) : <div className="imageBox-fadeBlocker">
- <img className="imageBox-fadeaway" key={"fadeaway"} ref={this._imgRef}
- src={fadepath}
- style={{ transform, transformOrigin }} draggable={false}
- width={nativeWidth} />
- </div>}
+ {fadepath === srcpath ? (null) :
+ <div className={`imageBox-fadeBlocker${this.props.isHovering?.() ? "-hover" : ""}`}>
+ <img className="imageBox-fadeaway" key={"fadeaway"}
+ src={fadepath}
+ style={{ transform, transformOrigin }} draggable={false}
+ width={nativeWidth} />
+ </div>}
</div>
{this.considerDownloadIcon}
{this.considerGooglePhotosLink()}
diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx
index 7637d39be..d5f3f3b4e 100644
--- a/src/client/views/nodes/LinkDocPreview.tsx
+++ b/src/client/views/nodes/LinkDocPreview.tsx
@@ -46,7 +46,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
if (anchor1 && anchor2) {
linkTarget = Doc.AreProtosEqual(anchor1, this._linkSrc) || Doc.AreProtosEqual(anchor1?.annotationOn as Doc, this._linkSrc) ? anchor2 : anchor1;
}
- if (linkTarget?.annotationOn) {
+ if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { // want to show annotation context document if annotation is not text
linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => this._targetDoc = anno));
} else {
this._targetDoc = linkTarget;
@@ -84,9 +84,16 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
anchorDoc && DocServer.GetRefField(anchorDoc).then(action(anchor => {
if (anchor instanceof Doc && DocListCast(anchor.links).length) {
this._linkDoc = this._linkDoc ?? DocListCast(anchor.links)[0];
- this._linkSrc = anchor;
- const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
- this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ const automaticLink = this._linkDoc.linkRelationship === "automatic";
+ if (automaticLink) { // automatic links specify the target in the link info, not the source
+ const linkTarget = anchor;
+ this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget);
+ this._targetDoc = linkTarget;
+ } else {
+ this._linkSrc = anchor;
+ const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
+ this._targetDoc = linkTarget?.type === DocumentType.MARKER && linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget;
+ }
this._toolTipText = "";
}
}));
@@ -99,7 +106,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(() => this._linkDoc && LinkManager.Instance.deleteLink(this._linkDoc)));
}
nextHref = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1)));
+ setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => {
+ this._linkDoc = undefined;
+ this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1);
+ }));
}
followLink = (e: React.PointerEvent) => {
@@ -152,10 +162,10 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> {
return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? (null) :
<div className="linkDocPreview-inner">
{!this.props.showHeader ? (null) : this.previewHeader}
- <div className="linkDocPreview-preview-wrapper">
+ <div className="linkDocPreview-preview-wrapper" style={{ maxHeight: this._toolTipText ? "100%" : undefined, overflow: "auto" }}>
{this._toolTipText ? this._toolTipText :
<DocumentView ref={(r) => {
- const targetanchor = LinkManager.getOppositeAnchor(this._linkDoc!, this._linkSrc!);
+ const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc);
targetanchor && this._targetDoc !== targetanchor && r?.focus(targetanchor);
}}
Document={this._targetDoc!}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 1f7e557d9..ec88b8d1d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -381,8 +381,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
// creates links between terms in a document and documents which have a matching Id
hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set<Doc>) => {
- if (this._editorView && (this._editorView as any).docView) {
- const flattened1 = this.findInNode(this._editorView, this._editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
+ const editorView = this._editorView;
+ if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
+ const flattened1 = this.findInNode(editorView, editorView.state.doc, StrCast(target.title).replace(/^@/, ""));
var alink: Doc | undefined;
flattened1.forEach((flat, i) => {
const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, StrCast(target.title).replace(/^@/, ""));
@@ -391,9 +392,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) &&
Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, "automatic")!);
newAutoLinks.add(alink);
- const allAnchors = [{ href: Doc.localServerPath(this.props.Document), title: "a link", anchorId: this.props.Document[Id] }];
- const link = this._editorView!.state.schema.marks.autoLinkAnchor.create({ allAnchors, linkDoc: alink[Id], title: "auto link", location });
- tr = tr.addMark(flattened[i].from, flattened[i].to, link);
+ const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
+ const sel = flattened[i];
+ tr = tr.addMark(sel.from, sel.to, splitter);
+ 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 allAnchors = [{ href: Doc.localServerPath(target), title: "a link", anchorId: this.props.Document[Id] }];
+ allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
+ const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: "auto term", location: "add:right" });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ tr = tr.removeMark(sel.from, sel.to, splitter);
});
}
return tr;
@@ -1443,6 +1453,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
} catch (e: any) { console.log(e.message); }
this._lastText = curText;
}
+ if (StrCast(this.rootDoc.title).startsWith("@") && !this.dataDoc["title-custom"]) {
+ UndoManager.RunInBatch(() => {
+ this.dataDoc["title-custom"] = true;
+ this.dataDoc.showTitle = "title";
+ const tr = this._editorView!.state.tr;
+ this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(0), tr.doc.resolve(StrCast(this.rootDoc.title).length + 2))).deleteSelection());
+ }, "titler");
+ }
}
onKeyDown = (e: React.KeyboardEvent) => {
// single line text boxes need to pass through tab/enter/backspace so that their containers can respond (eg, an outline container)
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index bafae84dc..00c03875b 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -294,6 +294,25 @@ export class RichTextRules {
return state.tr.deleteRange(start, end).insert(start, fieldView);
}),
+
+ // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document
+ // wiki:title
+ new InputRule(
+ new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/),
+ (state, match, start, end) => {
+ const title = match[1];
+ this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))));
+
+ this.TextBox.makeLinkAnchor(undefined, "add:right", `https://en.wikipedia.org/wiki/${title.trim()}`, "wikipedia reference");
+
+ const fstate = this.TextBox.EditorView?.state;
+ if (fstate) {
+ const tr = fstate?.tr.deleteRange(start, start + 5);
+ return tr.setSelection(new TextSelection(tr.doc.resolve(end - 5))).insertText(" ");
+ }
+ return state.tr;
+ }),
+
// create an inline view of a document {{ <layoutKey> : <Doc> }}
// {{:Doc}} => show default view of document
// {{<layout>}} => show layout for this doc
diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts
index 52ef06b7f..1f6ce014f 100644
--- a/src/client/views/nodes/formattedText/marks_rts.ts
+++ b/src/client/views/nodes/formattedText/marks_rts.ts
@@ -19,13 +19,17 @@ export const marks: { [index: string]: MarkSpec } = {
},
- // :: 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>`
+ // :: MarkSpec an autoLinkAnchor. These are automatically generated anchors to "published" documents based on the anchor text matching the
+ // published document's title.
+ // NOTE: unlike linkAnchors, the autoLinkAnchor's href's indicate the target anchor of the hyperlink and NOT the source. This is because
+ // automatic links do not create a text selection Marker document for the source anchor, but use the text document itself. Since
+ // multiple automatic links can be created each having the same source anchor (the whole document), the target href of the link is needed to
+ // disambiguate links from one another.
+ // Rendered and parsed as an `<a>`
// element.
autoLinkAnchor: {
attrs: {
allAnchors: { default: [] as { href: string, title: string, anchorId: string }[] },
- linkDoc: { default: "" },
location: { default: null },
title: { default: null },
},
@@ -44,7 +48,8 @@ export const marks: { [index: string]: MarkSpec } = {
return ["a", { class: anchorids, "data-targethrefs": targethrefs, "data-linkdoc": node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0];
}
},
- // :: 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`
+ // :: MarkSpec A linkAnchor. The anchor can have multiple links, where each linkAnchor specifies an href to the URL of the source selection Marker text,
+ // and a title for use in menus and hover. `title`
// defaults to the empty string. Rendered and parsed as an `<a>`
// element.
linkAnchor: {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 19de0fee9..0a91bf2ab 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -741,6 +741,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
//Regular click
@action
selectElement = async (doc: Doc) => {
+ console.log("got here", this.childDocs.indexOf(doc))
const context = Cast(doc.context, Doc, null);
this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0);
diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss
index 1ad4b820e..dd0520bad 100644
--- a/src/client/views/nodes/trails/PresElementBox.scss
+++ b/src/client/views/nodes/trails/PresElementBox.scss
@@ -195,6 +195,47 @@ $slide-active: #5B9FDD;
box-shadow: 0 0 0px 1.5px $dark-blue;
}
+.presItem-slide.group {
+ border-radius: 5px;
+}
+
+.presItem-slide.activeGroup {
+ border-radius: 5px;
+ box-shadow: 0 0 0px 1.5px $dark-blue;
+}
+
+.presItem-groupSlideContainer {
+ position: absolute;
+ /* grid-row: 3; */
+ /* grid-column: 1/8; */
+ top: 28;
+ display: block;
+ background: #92adb9;
+ width: 100%;
+ height: 10px;
+ border-radius: 0px 0px 5px 5px;
+ height: calc(100% - 28px);
+ display: grid;
+ grid-template-rows: auto auto auto;
+ grid-template-columns: 100%;
+ justify-items: left;
+ align-items: center;
+}
+
+.presItem-groupSlide {
+ position: relative;
+ background-color: #d5dce2;
+ border-radius: 5px;
+ height: calc(100% - 7px);
+ width: calc(100% - 20px);
+ left: 15px;
+ /* height: 20px; */
+ /* width: calc(100% - 19px); */
+ display: flex;
+ grid-template-rows: 16px 10px auto;
+ grid-template-columns: max-content max-content max-content max-content auto;
+}
+
.presItem-multiDrag {
font-family: Roboto;
font-weight: 600;
@@ -230,6 +271,37 @@ $slide-active: #5B9FDD;
box-shadow: 0 0 0px 1.5px $dark-blue;
}
-// .presItem-slide:hover {
-// background: $slide-hover;
-// } \ No newline at end of file
+.expandButton {
+ cursor: pointer;
+ position: absolute;
+ border-radius: 100px;
+ bottom: 0;
+ left: -18;
+ z-index: 300;
+ width: 15px;
+ height: 15px;
+ display: flex;
+ font-size: 12px;
+ justify-self: center;
+ align-self: center;
+ background-color: #92adb9;
+ color: white;
+ justify-content: center;
+ align-items: center;
+ transition: 0.2s;
+ margin-right: 3px;
+}
+
+.expandButton:hover {
+ background-color: rgba(0, 0, 0, 1);
+ transform: scale(1.2);
+}
+
+.presItem-groupNum {
+ color: #d5dce2;
+ position: absolute;
+ left: -15px;
+ top: 1;
+ font-weight: 600;
+ font-size: 12;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx
index a4ec559f5..1da7a596a 100644
--- a/src/client/views/nodes/trails/PresElementBox.tsx
+++ b/src/client/views/nodes/trails/PresElementBox.tsx
@@ -2,9 +2,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, Opt } from "../../../../fields/Doc";
+import { DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types";
import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils";
import { DocUtils } from "../../../documents/Documents";
import { DocumentType } from "../../../documents/DocumentTypes";
@@ -106,6 +106,38 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
</div>;
}
+ @computed get renderGroupSlides() {
+ const childDocs = DocListCast(this.targetDoc.data);
+ const groupSlides = childDocs.map((doc: Doc, ind: number) =>
+ <div className="presItem-groupSlide" onClick={(e) => {
+ console.log("Clicked on slide with index: ", ind);
+ e.stopPropagation();
+ e.preventDefault();
+ PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
+ }}>
+ <div className="presItem-groupNum">
+ {`${ind + 1}.`}
+ </div>
+ {/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */}
+ <div className="presItem-name">
+ <EditableView
+ ref={this._titleRef}
+ editing={undefined}
+ contents={doc.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(doc.title)}
+ SetValue={(value: string) => {
+ doc.title = !value.trim().length ? "-untitled-" : value;
+ return true;
+ }}
+ />
+ </div>
+ </div>
+ );
+ return (
+ groupSlides
+ );
+ }
@computed get duration() {
let durationInS: number;
if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; }
@@ -284,6 +316,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false;
const targetDoc: Doc = this.targetDoc;
const activeItem: Doc = this.rootDoc;
+ const isGroup: boolean = BoolCast(targetDoc._isGroup);
return (
<div className={`presItem-container`}
key={this.props.Document[Id] + this.indexInPres}
@@ -298,8 +331,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey);
}}
onDoubleClick={action(e => {
- this.toggleProperties();
- PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+ if (isGroup) {
+ this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup;
+ } else {
+ this.toggleProperties();
+ PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true);
+ }
})}
onPointerOver={this.onPointerOver}
onPointerLeave={this.onPointerLeave}
@@ -316,7 +353,46 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() {
<div className="presItem-number">
{`${this.indexInPres + 1}.`}
</div>}
- {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
+ {isGroup ?
+ <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "activeGroup" : "group"}`}
+ style={{
+ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
+ boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined
+ }}>
+ <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}>
+ <EditableView
+ ref={this._titleRef}
+ editing={!isSelected ? false : undefined}
+ contents={activeItem.title}
+ overflow={'ellipsis'}
+ GetValue={() => StrCast(activeItem.title)}
+ SetValue={this.onSetValue}
+ />
+ </div>
+ <div className={"presItem-slideButtons"}>
+ <Tooltip title={<><div className="dash-tooltip">{"Remove from presentation"}</div></>}><div
+ className={"slideButton"}
+ onClick={this.removeItem}>
+ <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} />
+ </div></Tooltip>
+ <div className="group"></div>
+ </div>
+ <div className="presItem-groupSlideContainer" style={{ top: 28, height: 'calc(100% - 28px)' }}>
+ {this.rootDoc.presExpandGroup ? this.renderGroupSlides : (null)}
+ </div>
+ <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div>
+ <div className="expandButton"
+ onClick={e => {
+ if (isGroup) {
+ this.rootDoc.presExpandGroup = !this.rootDoc.presExpandGroup;
+ }
+ }}
+ >
+ <FontAwesomeIcon icon={"caret-down"} style={{ transform: this.rootDoc.presExpandGroup ? "rotate(180deg)" : "rotate(0deg)" }} />
+ </div>
+ </div>
+ : (null)}
+ {miniView || isGroup ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`}
style={{
backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor),
boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined