aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authoralyssaf16 <alyssa_feinberg@brown.edu>2024-09-18 07:27:02 -0400
committeralyssaf16 <alyssa_feinberg@brown.edu>2024-09-18 07:27:02 -0400
commitfa10d872b2a63f5a53461c8fb5473706e78af4e9 (patch)
tree454827d08505299fdee8bcf17e90b290bd51422d /src/client/views/collections
parentb9adb3a47279e4bcb975240f3d37f3454cf0757a (diff)
parent0268a524540ff36bf007744eff6709dfcdedec96 (diff)
Merge branch 'alyssa-starter' of https://github.com/brown-dash/Dash-Web into alyssa-starter
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionCalendarView.tsx5
-rw-r--r--src/client/views/collections/CollectionCardDeckView.scss18
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx1
-rw-r--r--src/client/views/collections/CollectionCarouselView.scss46
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx374
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx3
-rw-r--r--src/client/views/collections/CollectionStackingView.scss7
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx62
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx26
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/collections/TabDocView.tsx175
-rw-r--r--src/client/views/collections/TreeView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx4
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss1
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss1
16 files changed, 355 insertions, 377 deletions
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 9eb16917b..0ea9f8ebc 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -38,8 +38,8 @@ export class CollectionCalendarView extends CollectionSubView() {
const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range);
const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range);
- const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr);
- const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr);
+ const { start: aFromDate, end: aToDate } = dateRangeStrToDates(aDateRangeStr);
+ const { start: bFromDate, end: bToDate } = dateRangeStrToDates(bDateRangeStr);
if (aFromDate > bFromDate) {
return -1; // a comes first
@@ -82,6 +82,7 @@ export class CollectionCalendarView extends CollectionSubView() {
isAnnotationOverlay={false}
// select={emptyFunction} What does this mean?
isAnyChildContentActive={returnTrue} // ??
+ dontCenter="y"
// childDocumentsActive={}
// whenChildContentsActiveChanged={}
childHideDecorationTitle={false}
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index a089b248d..cc797d0bd 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -6,6 +6,15 @@
position: relative;
background-color: white;
overflow: hidden;
+
+ button {
+ width: 35px;
+ height: 35px;
+ border-radius: 50%;
+ background-color: $dark-gray;
+ // border-color: $medium-blue;
+ margin: 5px; // transform: translateY(-50px);
+ }
}
.card-wrapper {
@@ -34,15 +43,6 @@
justify-content: start; /* Centers buttons horizontally */
}
-button {
- width: 35px;
- height: 35px;
- border-radius: 50%;
- background-color: $dark-gray;
- // border-color: $medium-blue;
- margin: 5px; // transform: translateY(-50px);
-}
-
// button:hover {
// transform: translateY(-50px);
// }
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index c799eb3c8..54cc02825 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -78,6 +78,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
NativeWidth={returnZero}
NativeHeight={returnZero}
fitWidth={undefined}
+ containerViewPath={this.childContainerViewPath}
onDoubleClickScript={this.onChildDoubleClick}
renderDepth={this._props.renderDepth + 1}
LayoutTemplate={this._props.childLayoutTemplate}
diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss
index b402a7a32..c40f471d6 100644
--- a/src/client/views/collections/CollectionCarouselView.scss
+++ b/src/client/views/collections/CollectionCarouselView.scss
@@ -23,6 +23,21 @@
}
}
+.collectionCarouselView-addFlashcards {
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ z-index: -1;
+ pointer-events: none;
+}
+.collectionCarouselView-recentlyMissed {
+ color: red;
+ z-index: 999;
+ position: relative;
+ left: 10px;
+ top: 10px;
+ pointer-events: none;
+}
.carouselView-back,
.carouselView-fwd,
.carouselView-star,
@@ -68,41 +83,52 @@
right: 52%;
}
.carouselView-quiz {
- position: absolute;
+ position: relative;
display: flex;
- top: 5px;
- right: 8px;
+ flex-direction: column;
+ height: 20px;
+ align-items: center;
+ margin: auto;
&:hover {
color: white;
}
}
.carouselView-practice {
- position: absolute;
+ position: relative;
display: flex;
- top: 22px;
- right: 8px;
+ flex-direction: column;
+ height: 20px;
+ align-items: center;
+ margin: auto;
&:hover {
color: white;
}
}
.carouselView-starFilter {
- position: absolute;
+ position: relative;
display: flex;
- top: 40px;
- right: 7px;
+ height: 20px;
+ align-items: center;
&:hover {
color: white;
}
}
+.carouselView-practiceModes {
+ width: 100%;
+ height: 40px;
+ display: flex;
+ flex-direction: column;
+}
.carouselView-menu {
position: absolute;
+ flex-direction: column;
+ align-items: center;
display: flex;
top: 2px;
right: 2px;
width: 30;
- height: 60;
border-radius: 5px;
color: rgba(255, 255, 255, 0.5);
background: rgba(0, 0, 0, 0.1);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 9f59322e8..91a7c6514 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,13 +1,12 @@
/* eslint-disable react/jsx-props-no-spreading */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, makeObservable, observable, trace } from 'mobx';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils';
-import { emptyFunction } from '../../../Utils';
+import { StopEvent, returnOne, returnZero } from '../../../ClientUtils';
import { Doc, Opt } from '../../../fields/Doc';
-import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { DragManager } from '../../util/DragManager';
@@ -19,15 +18,12 @@ import './CollectionCarouselView.scss';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
enum cardMode {
- // PRACTICE = 'practice',
STAR = 'star',
- // QUIZ = 'quiz',
ALL = 'all',
}
enum practiceMode {
PRACTICE = 'practice',
QUIZ = 'quiz',
- NORMAL = 'normal',
}
enum practiceVal {
MISSED = 'missed',
@@ -36,24 +32,20 @@ enum practiceVal {
@observer
export class CollectionCarouselView extends CollectionSubView() {
private _dropDisposer?: DragManager.DragDropDisposer;
- @observable private _practiceMessage: string | undefined;
- @observable private _filterMessage: string | undefined;
get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
- get sideField() { return "_" + this.fieldKey + "_usePath"; } // prettier-ignore
+ get sideField() { return "_" + this.fieldKey + "_usePath"; } // prettier-ignore
get starField() { return "star"; } // prettier-ignore
+ _fadeTimer: NodeJS.Timeout | undefined;
+
constructor(props: SubCollectionViewProps) {
super(props);
makeObservable(this);
- // this.setModes();
- this.layoutDoc.filterOp = cardMode.ALL;
- Doc.setDocFilter(this.Document, 'star', undefined, 'match');
- this.layoutDoc.practiceMode = practiceMode.NORMAL;
- this.layoutDoc._carousel_index = 0;
- this.carouselItems.forEach(item => { item.layout[this.practiceField] = undefined}); //prettier-ignore
- console.log(this.carouselItems.length);
}
+ @observable _last_index = this.carouselIndex;
+ @observable _last_opacity = 1;
+
componentWillUnmount() {
this._dropDisposer?.();
}
@@ -65,65 +57,46 @@ export class CollectionCarouselView extends CollectionSubView() {
}
};
- @computed get carouselItems() {
- this.childLayoutPairs.map(pair => {
- pair.layout.embedContainer = this.Document;
- });
- return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK);
+ @computed get practiceMode() {
+ return this.childDocs.some(doc => doc._layout_isFlashcard) ? StrCast(this.layoutDoc.practiceMode) : '';
}
- @computed get marginX() {
- return NumCast(this.layoutDoc.caption_xMargin, 50);
+ @computed get practiceMessage() {
+ const cardCount = this.carouselItems.length;
+ if (this.practiceMode) {
+ if (!Doc.hasDocFilter(this.layoutDoc, 'star') && !cardCount) {
+ return 'Finished! Click here to view all flashcards.';
+ }
+ }
+ return '';
}
- @action setPracticeMessage = (mes: string | undefined) => {
- this._practiceMessage = mes;
- };
- @action setFilterMessage = (mes: string | undefined) => {
- this._filterMessage = mes;
- };
-
- setModes = () => {
- this.layoutDoc.filterOp = cardMode.ALL;
- Doc.setDocFilter(this.Document, 'data_star', undefined, 'match');
- this.layoutDoc.practiceMode = practiceMode.NORMAL;
- this.layoutDoc._carousel_index = 0;
- };
-
- move = (dir: number) => {
- const moveToCardWithField = (match: (doc: Doc) => boolean): boolean => {
- let startInd = (NumCast(this.layoutDoc._carousel_index) + dir) % this.carouselItems.length;
- while (!match(this.carouselItems?.[startInd].layout) && (startInd + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) {
- startInd = (startInd + dir + this.carouselItems.length) % this.carouselItems.length;
+ @computed get filterMessage() {
+ const cardCount = this.carouselItems.length;
+ if (!this.practiceMessage) {
+ if (Doc.hasDocFilter(this.layoutDoc, 'star') && !cardCount) {
+ return 'No starred items. Click here to view all flash cards.';
}
- if (match(this.carouselItems?.[startInd].layout)) {
- this.layoutDoc._carousel_index = startInd;
- return true;
+ if (this.practiceMode) {
+ if (!cardCount) return 'No flashcards to show! Click here to leave practice mode';
}
- return match(this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout);
- };
-
- switch (this.layoutDoc.practiceMode && this.layoutDoc.filterOp) {
- case practiceMode.PRACTICE && cardMode.ALL:
- if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT)) {
- this._practiceMessage = 'Finished! Unselect practice mode to view all flashcards.';
- this.carouselItems.forEach(item => { item.layout[this.practiceField] = undefined}); //prettier-ignore
- }
- break;
- case !practiceMode.PRACTICE && cardMode.STAR:
- if (!moveToCardWithField((doc: Doc) => !!doc[this.starField])) {
- this._filterMessage = 'No starred items. Unselect this view to see all flashcards and star them.';
- }
- break;
- case practiceMode.PRACTICE && cardMode.STAR:
- if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT && doc[this.starField] === true)) {
- this._filterMessage = 'No flashcards to show! Unselect mode to view all flashcards.';
- this._practiceMessage = undefined;
- }
- break;
- default:
- moveToCardWithField(returnTrue);
}
- };
+ return '';
+ }
+ @computed get marginX() { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore
+ @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore
+ @computed get carouselItems() { return this.childDocs
+ .filter(doc => doc.type !== DocumentType.LINK)
+ .filter(doc => !this.practiceMode || (BoolCast(doc?._layout_isFlashcard) && doc[this.practiceField] !== practiceVal.CORRECT))// show only cards that aren't marked as correct
+ } // prettier-ignore
+
+ /**
+ * Move forward or backward the specified number of Docs
+ * @param dir signed number indicating Docs to move forward or backward
+ */
+ move = action((dir: number) => {
+ this._last_index = this.carouselIndex;
+ this.layoutDoc._carousel_index = this.carouselItems.length ? (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length : 0;
+ });
/**
* Goes to the next Doc in the stack subject to the currently selected filter option.
@@ -142,14 +115,12 @@ export class CollectionCarouselView extends CollectionSubView() {
};
/*
- * Stars the document when the star button is pressed.
+ * Toggles whether the 'star' metadata field is set on the current Doc
*/
- star = (e: React.MouseEvent) => {
+ toggleStar = (e: React.MouseEvent) => {
e.stopPropagation();
- const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)];
- curDoc.layout[this.starField] = curDoc.layout[this.starField] ? undefined : true;
- // if (!curDoc.layout[this.starField]) this.move(1);
- // this.layoutDoc._carousel_index = undefined;
+ const curDoc = this.carouselItems[this.carouselIndex];
+ curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true);
};
/*
@@ -157,81 +128,110 @@ export class CollectionCarouselView extends CollectionSubView() {
*/
setPracticeVal = (e: React.MouseEvent, val: string) => {
e.stopPropagation();
- const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)];
- curDoc.layout[this.practiceField] = val;
+ const curDoc = this.carouselItems[this.carouselIndex];
+ curDoc && (curDoc[this.practiceField] = val);
this.advance(e);
};
+ /**
+ * Sets the practice mode answer style for flashcards
+ * @param mode practiceMode or undefined for no practice
+ */
+ setPracticeMode = (mode: practiceMode | undefined) => {
+ this.layoutDoc.practiceMode = mode;
+ this.carouselItems?.map(doc => (doc[this.practiceField] = undefined));
+ if (mode === practiceMode.QUIZ) this.carouselItems?.map(doc => (doc[this.sideField] = undefined));
+ };
+
captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string) => {
// first look for properties on the document in the carousel, then fallback to properties on the container
const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined;
return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property);
};
- panelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0);
+ contentPanelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0) - 2 * NumCast(this.layoutDoc.yMargin);
onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
+ contentScreentToLocalXf = () => this._props.ScreenToLocalTransform().translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin));
- setPracticeMode = (mode: practiceMode) => {
- this.layoutDoc.practiceMode = mode;
- this.carouselItems?.map(doc => (doc.layout[this.practiceField] = undefined));
- switch (mode) {
- case practiceMode.QUIZ:
- this.carouselItems?.map(doc => (doc.layout[this.sideField] = undefined));
- break;
- case practiceMode.NORMAL:
- this.setPracticeMessage(undefined);
- break;
- }
- };
+ contentPanelWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin);
- setFilterMode = (mode: cardMode) => {
- this.layoutDoc.filterOp = mode;
- switch (mode) {
- case cardMode.STAR:
- // Doc.setDocFilter(this.Document, 'data_star', true, 'match');
- this.move(1);
- break;
- default:
- this.setFilterMessage(undefined); // prettier-ignore
- // Doc.setDocFilter(this.Document, 'data_star', true, 'remove');
- }
- };
+ isChildContentActive = () =>
+ this._props.isContentActive?.() === false
+ ? false
+ : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive))
+ ? true
+ : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
+ ? false
+ : undefined;
+ renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
+ return (
+ <DocumentView
+ {...this._props}
+ ref={overlayFunc}
+ Document={doc}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ fitWidth={undefined}
+ containerViewPath={this.childContainerViewPath}
+ setContentViewBox={undefined}
+ onDoubleClickScript={this.onContentDoubleClick}
+ onClickScript={this.onContentClick}
+ isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
+ isContentActive={this.isChildContentActive}
+ hideCaptions={showCaptions}
+ renderDepth={this._props.renderDepth + 1}
+ LayoutTemplate={this._props.childLayoutTemplate}
+ LayoutTemplateString={this._props.childLayoutString}
+ TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)}
+ childFilters={this.childDocFilters}
+ hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
+ addDocument={this._props.addDocument}
+ ScreenToLocalTransform={this.contentScreentToLocalXf}
+ PanelWidth={this.contentPanelWidth}
+ PanelHeight={this.contentPanelHeight}
+ />
+ );
+ };
+ /**
+ * Display an overlay of the previous card that crossfades to the next card
+ */
+ @computed get overlay() {
+ const fadeTime = 500;
+ const lastDoc = this.carouselItems?.[this._last_index];
+ return !lastDoc || this.carouselIndex === this._last_index ? null : (
+ <div className="collectionCarouselView-image" style={{ opacity: this._last_opacity, position: 'absolute', top: 0, left: 0, transition: `opacity ${fadeTime}ms` }}>
+ {this.renderDoc(
+ lastDoc,
+ false, // hide captions if the carousel is configured to show the captions
+ action((r: DocumentView | null) => {
+ if (r) {
+ this._fadeTimer && clearTimeout(this._fadeTimer);
+ this._last_opacity = 0;
+ this._fadeTimer = setTimeout(
+ action(() => {
+ this._last_index = -1;
+ this._last_opacity = 1;
+ }),
+ fadeTime
+ );
+ }
+ })
+ )}
+ </div>
+ );
+ }
@computed get content() {
- trace();
- if (this.layoutDoc._carousel_index === this.carouselItems.length && this.layoutDoc._carousel_index !== 0) {
- this.move(1);
- }
- const index = NumCast(this.layoutDoc._carousel_index);
+ const index = this.carouselIndex;
const curDoc = this.carouselItems?.[index];
const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined };
const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption);
-
- return !(curDoc?.layout instanceof Doc) ? null : (
+ return !curDoc ? null : (
<>
- <div className="collectionCarouselView-image">
- <DocumentView
- {...this._props}
- NativeWidth={returnZero}
- NativeHeight={returnZero}
- fitWidth={undefined}
- setContentViewBox={undefined}
- childFilters={this.childDocFilters}
- containerViewPath={this._props.docViewPath}
- onDoubleClickScript={this.onContentDoubleClick}
- onClickScript={this.onContentClick}
- isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
- isContentActive={(this._props.childContentsActive ?? this._props.isContentActive() === false) ? returnFalse : emptyFunction}
- addDocument={this._props.addDocument}
- hideCaptions={!!carouselShowsCaptions} // hide captions if the carousel is configured to show the captions
- renderDepth={this._props.renderDepth + 1}
- LayoutTemplate={this._props.childLayoutTemplate}
- LayoutTemplateString={this._props.childLayoutString}
- Document={curDoc.layout}
- TemplateDataDocument={DocCast(curDoc.layout.resolvedDataDoc)}
- PanelHeight={this.panelHeight}
- />
+ <div className="collectionCarouselView-image" key="image">
+ {this.renderDoc(curDoc, !!carouselShowsCaptions)}
+ {this.overlay}
</div>
{!carouselShowsCaptions ? null : (
<div
@@ -244,17 +244,13 @@ export class CollectionCarouselView extends CollectionSubView() {
marginLeft: this.marginX,
width: `calc(100% - ${this.marginX * 2}px)`,
}}>
- <FormattedTextBox key={index} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc.layout} TemplateDataDocument={undefined} />
+ <FormattedTextBox key={index} xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={curDoc} TemplateDataDocument={undefined} />
</div>
)}
</>
);
}
- containsDifTypes = (): boolean => {
- return this.carouselItems.filter(doc => !doc.layout._layout_isFlashcard).length !== 0;
- };
-
addFlashcard() {
const newDoc = Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 });
this.addDocument?.(newDoc);
@@ -270,30 +266,28 @@ export class CollectionCarouselView extends CollectionSubView() {
}
@computed get buttons() {
- if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null;
+ if (!this.carouselItems?.[this.carouselIndex]) return null;
return (
<>
+ <div>
+ <Tooltip title="star">
+ <div key="star" className="carouselView-star" onClick={this.toggleStar}>
+ <FontAwesomeIcon icon="star" color={this.carouselItems[this.carouselIndex]?.[this.starField] ? 'yellow' : 'gray'} size="1x" />
+ </div>
+ </Tooltip>
+ {/* <Tooltip title="add new flashcard to pile">
+ <div key="add" className="carouselView-add" onClick={this.addFlashcard}>
+ <FontAwesomeIcon icon="plus" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
+ </div>
+ </Tooltip> */}
+ </div>
<div key="back" className="carouselView-back" onClick={this.goback}>
<FontAwesomeIcon icon="chevron-left" size="2x" />
</div>
<div key="fwd" className="carouselView-fwd" onClick={this.advance}>
<FontAwesomeIcon icon="chevron-right" size="2x" />
</div>
- {!this.containsDifTypes() ? (
- <div>
- <Tooltip title="star">
- <div key="star" className="carouselView-star" onClick={this.star}>
- <FontAwesomeIcon icon="star" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
- </div>
- </Tooltip>
- {/* <Tooltip title="add new flashcard to pile">
- <div key="add" className="carouselView-add" onClick={this.addFlashcard}>
- <FontAwesomeIcon icon="plus" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
- </div>
- </Tooltip> */}
- </div>
- ) : null}
- {this.layoutDoc.practiceMode === practiceMode.PRACTICE ? (
+ {this.practiceMode ? (
<div>
<Tooltip title="Incorrect. View again later.">
<div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)}>
@@ -311,33 +305,31 @@ export class CollectionCarouselView extends CollectionSubView() {
);
}
- togglePracticeMode = (mode: practiceMode) => {
- if (mode === this.layoutDoc.practiceMode) {
- this.setPracticeMode(practiceMode.NORMAL);
- // this.setPracticeMessage("undefined");
- } else this.setPracticeMode(mode);
- };
- toggleFilterMode = () => { this.layoutDoc.filterOp === cardMode.STAR ? this.setFilterMode(cardMode.ALL) : this.setFilterMode(cardMode.STAR)}; //prettier-ignore
+ togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode);
+ toggleFilterMode = () => Doc.setDocFilter(this.Document, 'star', true, 'match', true);
setColor = (mode: practiceMode | cardMode, which: string) => { return which === mode ? 'white' : 'light gray'}; //prettier-ignore
@computed get menu() {
+ const curDoc = this.carouselItems?.[this.carouselIndex];
return (
<div className="carouselView-menu">
- <Tooltip title="Practice flashcards using GPT">
- <div key="back" className="carouselView-quiz" onClick={e => this.togglePracticeMode(practiceMode.QUIZ)}>
- <FontAwesomeIcon icon="file-pen" color={this.setColor(practiceMode.QUIZ, StrCast(this.layoutDoc.practiceMode))} size="1x" />
- </div>
- </Tooltip>
- <Tooltip title={this.layoutDoc.practiceMode === practiceMode.PRACTICE ? 'Exit practice mode' : 'Practice flashcards manually'}>
- <div key="back" className="carouselView-practice" onClick={e => this.togglePracticeMode(practiceMode.PRACTICE)}>
- <FontAwesomeIcon icon="check" color={this.setColor(practiceMode.PRACTICE, StrCast(this.layoutDoc.practiceMode))} size="1x" />
- </div>
- </Tooltip>
- <Tooltip title={this.layoutDoc.filterOp === cardMode.STAR ? 'Show all cards' : 'Show only starred cards'}>
+ <Tooltip title={Doc.hasDocFilter(this.Document, 'star') ? 'Show all cards' : 'Show only starred cards'}>
<div key="back" className="carouselView-starFilter" onClick={e => this.toggleFilterMode()}>
- <FontAwesomeIcon icon="filter" color={this.setColor(cardMode.STAR, StrCast(this.layoutDoc.filterOp))} size="1x" />
+ <FontAwesomeIcon icon="filter" color={Doc.hasDocFilter(this.Document, 'star') ? 'white' : 'lightgray'} size="1x" />
</div>
</Tooltip>
+ <div className="carouselView-practiceModes" style={{ display: BoolCast(curDoc?._layout_isFlashcard) ? undefined : 'none' }}>
+ <Tooltip title="Practice flashcards using GPT">
+ <div key="back" className="carouselView-quiz" onClick={e => this.togglePracticeMode(practiceMode.QUIZ)}>
+ <FontAwesomeIcon icon="file-pen" color={this.setColor(practiceMode.QUIZ, StrCast(this.practiceMode))} size="1x" />
+ </div>
+ </Tooltip>
+ <Tooltip title={this.practiceMode === practiceMode.PRACTICE ? 'Exit practice mode' : 'Practice flashcards manually'}>
+ <div key="back" className="carouselView-practice" onClick={e => this.togglePracticeMode(practiceMode.PRACTICE)}>
+ <FontAwesomeIcon icon="check" color={this.setColor(practiceMode.PRACTICE, StrCast(this.practiceMode))} size="1x" />
+ </div>
+ </Tooltip>
+ </div>
</div>
);
}
@@ -351,36 +343,22 @@ export class CollectionCarouselView extends CollectionSubView() {
background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
}}>
- {!this._practiceMessage && !this._filterMessage ? (
+ {!this.practiceMessage && !this.filterMessage ? (
this.content
) : (
- <p className="message">
- {this._filterMessage}
- {'\n'}
- {this._practiceMessage}
+ <p
+ className="message"
+ onClick={e => {
+ if (this.filterMessage || this.practiceMessage) {
+ this.setPracticeMode(undefined);
+ Doc.setDocFilter(this.layoutDoc, 'star', undefined, 'remove');
+ }
+ }}>
+ {this.filterMessage || this.practiceMessage}
</p>
)}
- {!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)] && this.layoutDoc.practiceMode === practiceMode.PRACTICE ? <p className="message">Add flashcards </p> : null}
- <p
- className="missed-message"
- style={{
- color: 'red',
- fontWeight: 'bold',
- zIndex: '999',
- position: 'relative',
- left: '10px',
- top: '10px',
- width: '10px',
- display: this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]
- ? this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.practiceField] === practiceVal.MISSED && this.layoutDoc.practiceMode === practiceMode.PRACTICE && !this._practiceMessage
- ? 'block'
- : 'none'
- : 'none',
- }}>
- Recently missed!
- </p>
- {!this.containsDifTypes() && this.carouselItems.length !== 0 ? this.menu : null}
- {this.Document._chromeHidden || (!this._filterMessage && !this._practiceMessage) ? this.buttons : null}
+ {!this.Document._chromeHidden ? this.menu : null}
+ {!this.Document._chromeHidden ? this.buttons : null}
</div>
);
}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index e0aa79c7b..028133a6e 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -31,6 +31,7 @@ import { ScriptingRepl } from '../ScriptingRepl';
import { UndoStack } from '../UndoStack';
import './CollectionDockingView.scss';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { TabHTMLElement } from './TabDocView';
@observer
export class CollectionDockingView extends CollectionSubView() {
@@ -544,7 +545,7 @@ export class CollectionDockingView extends CollectionSubView() {
tabCreated = (tab: { contentItem: { element: HTMLElement[] } }) => {
this.tabMap.add(tab);
// InitTab is added to the tab's HTMLElement in TabDocView
- const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as { InitTab?: (tab: object) => void };
+ const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as TabHTMLElement;
tabdocviewContent?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content)
};
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 6225cc52a..6400a0a8e 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -54,11 +54,8 @@
}
.collectionStackingViewFieldColumn {
- height: max-content;
- }
-
- .collectionStackingViewFieldColumnDragging {
- height: 100%;
+ display: flex;
+ flex-direction: column;
}
.collectionSchemaView-previewDoc {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 6402ef16c..e97ee713e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -35,6 +35,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow';
import './CollectionStackingView.scss';
import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn';
import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { computedFn } from 'mobx-utils';
export type collectionStackingViewProps = {
sortFunc?: (a: Doc, b: Doc) => number;
@@ -127,6 +128,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
}
+ columnWidthFn = () => this.columnWidth;
+ columnDocHeightFn = (doc: Doc) => () => (this.isStackingView ? this.getDocHeight(doc)() : Math.min(this.getDocHeight(doc)(), this._props.PanelHeight()));
+
// TODO: plj - these are the children
children = (docs: Doc[]) => {
// TODO: can somebody explain me to what exactly TraceMobX is?
@@ -140,17 +144,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
})
);
return docs.map((d, i) => {
- const height = () => this.getDocHeight(d);
- const width = () => this.getDocWidth(d);
- const trans = () => this.getDocTransition(d);
// assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns
- const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
+ const rowSpan = Math.ceil((this.getDocHeight(d)() + this.gridGap) / this.gridGap);
// just getting the style
- const style = this.isStackingView ? { margin: this.Document._stacking_alignCenter ? 'auto' : undefined, transition: trans(), width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` };
+ const style = this.isStackingView ? { margin: undefined, transition: this.getDocTransition(d)(), width: this.columnWidth, marginTop: i ? this.gridGap : 0, height: this.getDocHeight(d)() } : { gridRowEnd: `span ${rowSpan}` };
// So we're choosing whether we're going to render a column or a masonry doc
return (
<div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}>
- {this.getDisplayDoc(d, width, trans, i)}
+ {this.getDisplayDoc(d, this.getDocTransition(d), i)}
</div>
);
});
@@ -311,26 +312,23 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
: this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
? false
: undefined;
+
isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined);
@observable docRefs = new ObservableMap<Doc, DocumentView>();
childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null));
// this is what renders the document that you see on the screen
// called in Children: this actually adds a document to our children list
- getDisplayDoc(doc: Doc, width: () => number, trans: () => string, count: number) {
+ getDisplayDoc(doc: Doc, trans: () => string, count: number) {
const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
- const height = () => this.getDocHeight(doc);
- const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight()));
- const panelWidth = () => this.columnWidth;
- const stackedDocTransform = () => this.getDocTransform(doc);
- this._docXfs.push({ stackedDocTransform, width, height });
+ this._docXfs.push({ stackedDocTransform: this.getDocTransform(doc), width: this.getDocWidth(doc), height: this.getDocHeight(doc) });
return count > this._renderCount ? null : (
<DocumentView
ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(doc, r))}
Document={doc}
TemplateDataDocument={dataDoc}
renderDepth={this._props.renderDepth + 1}
- PanelWidth={panelWidth}
- PanelHeight={panelHeight}
+ PanelWidth={this.columnWidthFn}
+ PanelHeight={this.columnDocHeightFn(doc)}
pointerEvents={this.DocumentView?.()._props.onClickScript?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView)
styleProvider={this.styleProvider}
containerViewPath={this.childContainerViewPath}
@@ -341,16 +339,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
isDocumentActive={this.isContentActive}
LayoutTemplate={this._props.childLayoutTemplate}
LayoutTemplateString={this._props.childLayoutString}
- NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
- NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? height : undefined}
- dontCenter={this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy')}
+ NativeWidth={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeWidth(doc)) ? this.getDocWidth(doc) : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+ NativeHeight={this._props.childIgnoreNativeSize ? returnZero : this._props.childLayoutFitWidth?.(doc) || (this.childFitWidth(doc) && !Doc.NativeHeight(doc)) ? this.getDocHeight(doc) : undefined}
+ dontCenter={this.dontCenter}
dontRegisterView={BoolCast(this.layoutDoc.childDontRegisterViews, this._props.dontRegisterView)} // used to be true if DataDoc existed, but template textboxes won't layout_autoHeight resize if dontRegisterView is set, but they need to.
rootSelected={this.rootSelected}
showTitle={this._props.childlayout_showTitle}
dragAction={(this.layoutDoc.childDragAction ?? this._props.childDragAction) as dropActionType}
onClickScript={this.onChildClickHandler}
onDoubleClickScript={this.onChildDoubleClickHandler}
- ScreenToLocalTransform={stackedDocTransform}
+ ScreenToLocalTransform={this.getDocTransform(doc)}
focus={this.focusDocument}
childFilters={this.childDocFilters}
hideDecorationTitle={this._props.childHideDecorationTitle}
@@ -371,15 +369,18 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
);
}
- getDocTransform(doc: Doc) {
+ getDocTransform = computedFn((doc: Doc) => () => {
+ // these must be referenced for document decorations to update when the text box container is scrolled
+ this._scroll;
+ this._props.ScreenToLocalTransform();
+
const dref = this.docRefs.get(doc);
- this._scroll; // must be referenced for document decorations to update when the text box container is scrolled
const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
return new Transform(-translateX + (dref?.centeringX || 0) * scale,
-translateY + (dref?.centeringY || 0) * scale, 1)
.scale(1 / scale); // prettier-ignore
- }
- getDocWidth(d?: Doc) {
+ });
+ getDocWidth = computedFn((d?: Doc) => () => {
if (!d) return 0;
const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
const maxWidth = this.columnWidth / this.numGroupColumns;
@@ -387,12 +388,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
return Math.min(NumCast(d._width), maxWidth);
}
return maxWidth;
- }
- getDocTransition(d?: Doc) {
- if (!d) return '';
- return StrCast(d.dataTransition);
- }
- getDocHeight(d?: Doc) {
+ });
+ getDocTransition = computedFn((d?: Doc) => () => StrCast(d?.dataTransition));
+ getDocHeight = computedFn((d?: Doc) => () => {
if (!d || d.hidden) return 0;
const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.());
const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument;
@@ -401,13 +399,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0);
if (nw && nh) {
const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
- const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
+ const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d)(), colWid);
return Math.min(maxHeight, (docWid * nh) / nw);
}
const childHeight = NumCast(childLayoutDoc._height);
const panelHeight = this.childFitWidth(childLayoutDoc) ? Number.MAX_SAFE_INTEGER : this._props.PanelHeight() - 2 * this.yMargin;
return Math.min(childHeight, maxHeight, panelHeight);
- }
+ });
// This following three functions must be from the view Mehek showed
columnDividerDown = (e: React.PointerEvent) => {
@@ -531,6 +529,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
}
});
};
+ @computed get dontCenter() {
+ return this._props.dontCenter ?? (this._props.childIgnoreNativeSize ? 'xy' : (StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'));
+ }
headings = () => Array.from(this.Sections);
// what a section looks like if we're in stacking view
sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
@@ -565,6 +566,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
type={type}
createDropTarget={this.createDashEventsTarget}
screenToLocalTransform={this.ScreenToLocalBoxXf}
+ dontCenter={this.dontCenter}
/>
);
};
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 5ae08e535..ed0cabd0a 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -41,6 +41,7 @@ interface CSVFieldColumnProps {
columnWidth: number;
numGroupColumns: number;
gridGap: number;
+ dontCenter: 'x' | 'xy' | 'y';
type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
headings: () => object[];
// I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure
@@ -345,15 +346,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
<button type="button" className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
<FontAwesomeIcon icon="trash" size="lg" />
</button>
- {/* {evContents === noValueHeader ? null : (
- <div className="collectionStackingView-sectionOptions">
- <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}>
- <button className="collectionStackingView-sectionOptionButton">
- <FontAwesomeIcon icon="ellipsis-v" size="lg"></FontAwesomeIcon>
- </button>
- </Flyout>
- </div>
- )} */}
</div>
<div
className={'collectionStackingView-collapseBar' + (this._props.headingObject.collapsed === true ? ' active' : '')}
@@ -368,14 +360,20 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
<>
{this._props.Document._columnsHideIfEmpty ? null : headingView}
{this.collapsed ? null : (
- <div>
+ <div
+ style={{
+ margin: 'auto',
+ marginTop: this._props.dontCenter.includes('y') ? undefined : 'auto',
+ marginBottom: this._props.dontCenter.includes('y') ? undefined : 'auto',
+ width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1),
+ }}>
<div
key={`${heading}-stack`}
className="collectionStackingView-masonrySingle"
style={{
padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`,
- margin: 'auto',
- width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ margin: this._props.dontCenter.includes('x') ? undefined : 'auto',
+ // width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
height: 'max-content',
position: 'relative',
gridGap: this._props.gridGap,
@@ -419,11 +417,11 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent<
const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
return (
<div
- className={'collectionStackingViewFieldColumn' + (SnappingManager.IsDragging ? 'Dragging' : '')}
+ className="collectionStackingViewFieldColumn"
key={heading}
style={{
width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`,
- height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
+ height: undefined,
background: this._background,
}}
ref={this.createColumnDropRef}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index ab93abab6..c9e934448 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -34,6 +34,7 @@ import { CollectionLinearView } from './collectionLinear';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
+import { CalendarBox } from '../nodes/calendarBox/CalendarBox';
@observer
export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() {
@@ -91,7 +92,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
if (type === undefined) return null;
switch (type) {
case CollectionViewType.Schema: return <CollectionSchemaView key="collview" {...props} />;
- case CollectionViewType.Calendar: return <CollectionCalendarView key="collview" {...props} />;
+ case CollectionViewType.Calendar: return <CalendarBox key="collview" {...props} />;
case CollectionViewType.Docking: return <CollectionDockingView key="collview" {...props} />;
case CollectionViewType.Tree: return <CollectionTreeView key="collview" {...props} />;
case CollectionViewType.Multicolumn: return <CollectionMulticolumnView key="collview" {...props} />;
@@ -126,6 +127,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
{ description: 'Masonry', event: () => func(CollectionViewType.Masonry), icon: 'columns' },
{ description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' },
{ description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' },
+ { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns' },
{ description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' },
{ description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' },
{ description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' },
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 31b6be927..f56ea9d76 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -57,6 +57,7 @@ interface TabMiniThumbProps {
miniLeft: () => number;
}
+export type TabHTMLElement = HTMLDivElement & { InitTab?: (tab: object) => void };
@observer
class TabMiniThumb extends React.Component<TabMiniThumbProps> {
render() {
@@ -193,8 +194,9 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
.filter(tv => tv._document)
.map(tv => tv._document!);
}
- _mainCont: HTMLDivElement | null = null;
+ _mainCont: TabHTMLElement | null = null;
_tabReaction: IReactionDisposer | undefined;
+ _lastSelection = 0; // time when view was last selected - used to re-select views that get invalidated when selected
/**
* Adds a document to the presentation view
@@ -273,19 +275,24 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs
}
+ // Flag indicating that when a tab is activated, it should not select it's document.
+ // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
+ public static DontSelectOnActivate = 'dontSelectOnActivate';
+
+ public static IsSelected = (doc?: Doc) => {
+ return DocumentView.getViews(doc).some(dv => dv?.IsSelected);
+ };
+
static Activate = (tabDoc: Doc) => {
const tab = Array.from(CollectionDockingView.Instance?.tabMap ?? []).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue);
tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
return tab !== undefined;
};
- // static ActivateTabView(doc: Doc) {
- // const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc);
- // if (!tabView?._activated && tabView?._document) {
- // TabDocView.Activate(tabView?._document);
- // return tabView;
- // }
- // return undefined;
- // }
+
+ get stack() { return this._props.glContainer.parent.parent; } // prettier-ignore
+ get tab() { return this._props.glContainer.tab; } // prettier-ignore
+ get view() { return this._view; } // prettier-ignore
+
constructor(props: TabDocViewProps) {
super(props);
makeObservable(this);
@@ -299,37 +306,13 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
@observable _hovering = false;
@observable _isActive: boolean = false;
@observable _isAnyChildContentActive = false;
- public static IsSelected = (doc?: Doc) => {
- if (DocumentView.getViews(doc).some(dv => dv?.IsSelected)) {
- return true;
- }
- return false;
- };
- @computed get _isUserActivated() {
- return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive;
- }
- get _isContentActive() {
- return this._isUserActivated || this._hovering;
- }
@observable _document: Doc | undefined = undefined;
@observable _view: DocumentView | undefined = undefined;
+ @observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver. this hack allows the resize observer to trigger a react update
- @computed get layoutDoc() {
- return this._document && Doc.Layout(this._document);
- }
-
- get stack() {
- return this._props.glContainer.parent.parent;
- }
- get tab() {
- return this._props.glContainer.tab;
- }
- get view() {
- return this._view;
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- _lastTab: any;
- _lastView: DocumentView | undefined;
+ @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore
+ @computed get isUserActivated() { return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore
+ @computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore
@action
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -413,7 +396,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
color === variant
? DashColor(color)
.fade(
- this._isUserActivated
+ this.isUserActivated
? 0
: this._hovering
? 0.25
@@ -522,19 +505,14 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged);
}
- // Flag indicating that when a tab is activated, it should not select it's document.
- // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected)
- public static DontSelectOnActivate = 'dontSelectOnActivate';
-
- @action.bound
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- private onActiveContentItemChanged(contentItem: any) {
+ onActiveContentItemChanged = action((contentItem: any) => {
if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) {
this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab;
if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false));
!this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one.
}
- }
+ });
// adds a tab to the layout based on the locaiton parameter which can be:
// close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab,
@@ -551,7 +529,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
const panelName = whereFields.length > 1 ? whereFields.lastElement() : '';
if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]);
- // prettier-ignore
switch (whereFields[0]) {
case undefined:
case OpenWhere.lightbox: return LightboxView.Instance.AddDocTab(docs[0], location);
@@ -559,7 +536,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
case OpenWhere.replace: return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue);
case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue);
case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue);
- }
+ } // prettier-ignore
};
remDocTab = (doc: Doc | Doc[]) => {
if (doc === this._document) {
@@ -571,8 +548,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
};
getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame);
-
- @action
focusFunc = () => {
if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) {
this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost)
@@ -580,7 +555,6 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
return undefined;
};
active = () => this._isActive;
- @observable _forceInvalidateScreenToLocal = 0;
ScreenToLocalTransform = () => {
this._forceInvalidateScreenToLocal;
const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement);
@@ -594,47 +568,44 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
whenChildContentActiveChanges = (isActive: boolean) => {
this._isAnyChildContentActive = isActive;
};
- isContentActive = () => this._isContentActive;
+ isContentActiveFunc = () => this.isContentActive;
waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
- @computed get docView() {
- return !this._activated || !this._document ? null : (
- <>
- <DocumentView
- key={this._document[Id]}
- ref={action((r: DocumentView) => {
- this._lastView && DocumentView.removeView(this._lastView);
- this._view = r;
- this._lastView = this._view;
- })}
- renderDepth={0}
- LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
- hideTitle={this._props.keyValue}
- Document={this._document}
- TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined}
- waitForDoubleClickToClick={this.waitForDoubleClick}
- isContentActive={this.isContentActive}
- isDocumentActive={returnFalse}
- PanelWidth={this.PanelWidth}
- PanelHeight={this.PanelHeight}
- styleProvider={DefaultStyleProvider}
- childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
- childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
- searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
- addDocument={undefined}
- removeDocument={this.remDocTab}
- addDocTab={this.addDocTab}
- suppressSetHeight={!!this._document._layout_fitWidth}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- dontCenter="y"
- whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
- focus={this.focusFunc}
- containerViewPath={returnEmptyDocViewList}
- pinToPres={TabDocView.PinDoc}
- />
- {this.disableMinimap() ? null : <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />}
- </>
- );
- }
+ renderDocView = (doc: Doc) => (
+ <DocumentView
+ key={doc[Id]}
+ ref={action((r: DocumentView) => {
+ const now = Date.now();
+ this._lastSelection = this._view?.IsSelected ? now : this._lastSelection;
+ if (this._view) DocumentView.removeView(this._view);
+ this._view = r;
+ if (this._view && now - this._lastSelection < 1000) this._view.select(false);
+ })}
+ renderDepth={0}
+ LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined}
+ hideTitle={this._props.keyValue}
+ Document={doc}
+ TemplateDataDocument={!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined}
+ waitForDoubleClickToClick={this.waitForDoubleClick}
+ isContentActive={this.isContentActiveFunc}
+ isDocumentActive={returnFalse}
+ PanelWidth={this.PanelWidth}
+ PanelHeight={this.PanelHeight}
+ styleProvider={DefaultStyleProvider}
+ childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter}
+ childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter}
+ searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist}
+ addDocument={undefined}
+ removeDocument={this.remDocTab}
+ addDocTab={this.addDocTab}
+ suppressSetHeight={!!doc._layout_fitWidth}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ dontCenter="y"
+ whenChildContentsActiveChanged={this.whenChildContentActiveChanges}
+ focus={this.focusFunc}
+ containerViewPath={returnEmptyDocViewList}
+ pinToPres={TabDocView.PinDoc}
+ />
+ );
render() {
return (
@@ -647,23 +618,21 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore
onDragOver={action(() => { this._hovering = true; })} // prettier-ignore
onDragLeave={action(() => { this._hovering = false; })} // prettier-ignore
- ref={ref => {
+ ref={(ref: TabHTMLElement) => {
+ // "add" an InitTab function to this div to call from tabCreated in CollectionDockingView when div is reused
this._mainCont = ref;
if (this._mainCont) {
- if (this._lastTab) {
- this._view && DocumentView.removeView(this._view);
- }
- this._lastTab = this.tab;
- (this._mainCont as { InitTab?: (tab: object) => void }).InitTab = (tab: object) => this.init(tab, this._document);
- DocServer.GetRefField(this._props.documentId).then(
- action(doc => {
- doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
- })
- );
- ref && new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref);
+ this._mainCont.InitTab = (tab: object) => this.init(tab, this._document);
+ DocServer.GetRefField(this._props.documentId).then(action(doc => {
+ doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document);
+ })); // prettier-ignore
+ new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(this._mainCont);
}
}}>
- {this.docView}
+ {!this._activated || !this._document ? null : this.renderDocView(this._document)}
+ {this.disableMinimap() || !this._document ? null : (
+ <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} />
+ )}
</div>
);
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index b10a521ca..d2514dfd1 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -474,7 +474,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> {
};
docTransform = () => this.refTransform(this._dref?.ContentDiv);
getTransform = () => this.refTransform(this._tref.current);
- embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1);
+ embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1) - 3 /* paddingRight for bullet */;
embeddedPanelHeight = () => {
const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc;
return Math.min(
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index 717081666..534f67927 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -179,7 +179,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
})}>
{FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
+ const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png'];
return (
<div
className="image-wrapper"
@@ -264,6 +264,7 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() {
isContentActive={returnTrue}
isAnyChildContentActive={returnTrue}
childHideDecorations={true}
+ dontCenter="y"
/>
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index 94ec59ecb..753685b97 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -139,9 +139,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
toggleDisplayInformation = () => {
this._displayImageInformation = !this._displayImageInformation;
if (this._displayImageInformation) {
- this._selectedImages.forEach(doc => (doc[DocData].showTags = true));
+ this._selectedImages.forEach(doc => (doc._layout_showTags = true));
} else {
- this._selectedImages.forEach(doc => (doc[DocData].showTags = false));
+ this._selectedImages.forEach(doc => (doc._layout_showTags = false));
}
};
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
index 06d78c39e..9ed247d50 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss
@@ -1,5 +1,6 @@
.collectionMulticolumnView_drop {
height: 100%;
+ width: 100%;
top: 0;
left: 0;
position: absolute;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
index 0d49fabaa..91779065d 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss
@@ -1,5 +1,6 @@
.collectionMultirowView_drop {
height: 100%;
+ width: 100%;
top: 0;
left: 0;
position: absolute;