aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx4
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx193
-rw-r--r--src/client/views/collections/CollectionMenu.scss1255
-rw-r--r--src/client/views/collections/CollectionMenu.tsx179
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx575
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss142
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx776
-rw-r--r--src/client/views/collections/CollectionStackingView.scss24
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx119
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx12
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx101
-rw-r--r--src/client/views/collections/CollectionView.tsx17
-rw-r--r--src/client/views/collections/TabDocView.scss30
-rw-r--r--src/client/views/collections/TabDocView.tsx24
-rw-r--r--src/client/views/collections/TreeView.tsx38
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx19
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx102
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx40
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/index.ts7
-rw-r--r--src/client/views/collections/collectionGrid/index.ts2
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.scss (renamed from src/client/views/collections/CollectionLinearView.scss)63
-rw-r--r--src/client/views/collections/collectionLinear/CollectionLinearView.tsx225
-rw-r--r--src/client/views/collections/collectionLinear/index.ts1
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx5
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx82
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx6
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss123
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.tsx9
-rw-r--r--src/client/views/collections/collectionSchema/SchemaTable.tsx20
33 files changed, 2260 insertions, 1989 deletions
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index cae08e1f4..5325d5827 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -428,7 +428,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
const emptyPane = CurrentUserUtils.EmptyPane;
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`,
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
CollectionDockingView.AddSplit(docToAdd, "", stack);
@@ -453,7 +453,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) {
const emptyPane = CurrentUserUtils.EmptyPane;
emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1;
const docToAdd = Docs.Create.FreeformDocument([], {
- _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
+ _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`
});
this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd);
CollectionDockingView.AddSplit(docToAdd, "", stack);
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
deleted file mode 100644
index 52c836556..000000000
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ /dev/null
@@ -1,193 +0,0 @@
-import { Tooltip } from '@material-ui/core';
-import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import * as React from 'react';
-import { Doc, HeightSym, WidthSym } from '../../../fields/Doc';
-import { documentSchema } from '../../../fields/documentSchemas';
-import { Id } from '../../../fields/FieldSymbols';
-import { makeInterface } from '../../../fields/Schema';
-import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, Utils } from '../../../Utils';
-import { DragManager } from '../../util/DragManager';
-import { Transform } from '../../util/Transform';
-import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
-import { DocumentView } from '../nodes/DocumentView';
-import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
-import { StyleProp } from '../StyleProvider';
-import "./CollectionLinearView.scss";
-import { CollectionSubView } from './CollectionSubView';
-import { CollectionViewType } from './CollectionView';
-
-
-type LinearDocument = makeInterface<[typeof documentSchema,]>;
-const LinearDocument = makeInterface(documentSchema);
-
-@observer
-export class CollectionLinearView extends CollectionSubView(LinearDocument) {
- @observable public addMenuToggle = React.createRef<HTMLInputElement>();
- @observable private _selectedIndex = -1;
- private _dropDisposer?: DragManager.DragDropDisposer;
- private _widthDisposer?: IReactionDisposer;
- private _selectedDisposer?: IReactionDisposer;
-
- componentWillUnmount() {
- this._dropDisposer?.();
- this._widthDisposer?.();
- this._selectedDisposer?.();
- this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
- }
-
- componentDidMount() {
- this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10),
- width => this.childDocs.length && (this.layoutDoc._width = width),
- { fireImmediately: true }
- );
-
- this._selectedDisposer = reaction(
- () => NumCast(this.layoutDoc.selectedIndex),
- (i) => runInAction(() => {
- this._selectedIndex = i;
- let selected: any = undefined;
- this.childLayoutPairs.map(async (pair, ind) => {
- const isSelected = this._selectedIndex === ind;
- if (isSelected) {
- selected = pair;
- }
- else {
- ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
- }
- });
- if (selected && selected.layout) {
- ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
- }
- }),
- { fireImmediately: true }
- );
- }
- protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
- this._dropDisposer && this._dropDisposer();
- if (ele) {
- this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
- }
- }
-
- dimension = () => NumCast(this.rootDoc._height); // 2 * the padding
- getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
- if (!ele.current) return Transform.Identity();
- const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
- return new Transform(-translateX, -translateY, 1);
- }
-
- @action
- exitLongLinks = () => {
- if (DocumentLinksButton.StartLink) {
- if (DocumentLinksButton.StartLink.Document) {
- action((e: React.PointerEvent<HTMLDivElement>) => {
- Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
- });
- }
- }
- DocumentLinksButton.StartLink = undefined;
- DocumentLinksButton.StartLinkView = undefined;
- }
-
- @action
- changeDescriptionSetting = () => {
- if (LinkDescriptionPopup.showDescriptions) {
- if (LinkDescriptionPopup.showDescriptions === "ON") {
- LinkDescriptionPopup.showDescriptions = "OFF";
- LinkDescriptionPopup.descriptionPopup = false;
- } else {
- LinkDescriptionPopup.showDescriptions = "ON";
- }
- } else {
- LinkDescriptionPopup.showDescriptions = "OFF";
- LinkDescriptionPopup.descriptionPopup = false;
- }
- }
-
- render() {
- const guid = Utils.GenerateGuid();
- const flexDir: any = StrCast(this.Document.flexDirection);
- const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
- const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
-
- const menuOpener = <label htmlFor={`${guid}`} style={{ pointerEvents: "all", cursor: "pointer", background: backgroundColor === color ? "black" : backgroundColor, }}
- onPointerDown={e => e.stopPropagation()} >
- <p>{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "–" : "+"}</p>
- </label>;
-
- return <div className="collectionLinearView-outer">
- <div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top">
- {menuOpener}
- </Tooltip>
- <input id={`${guid}`} type="checkbox" checked={BoolCast(this.layoutDoc.linearViewIsExpanded)} ref={this.addMenuToggle}
- onChange={action(() => this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
-
- <div className="collectionLinearView-content" style={{ height: this.dimension(), flexDirection: flexDir }}>
- {this.childLayoutPairs.map((pair, ind) => {
- const nested = pair.layout._viewType === CollectionViewType.Linear;
- const dref = React.createRef<HTMLDivElement>();
- const scalable = pair.layout.onClick || pair.layout.onDragStart;
- return <div className={`collectionLinearView-docBtn` + (scalable ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
- style={{
- pointerEvents: "all",
- minWidth: 30,
- width: nested ? pair.layout[WidthSym]() : this.dimension(),
- height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension(),
- }} >
- <DocumentView
- Document={pair.layout}
- DataDoc={pair.data}
- isContentActive={returnFalse}
- isDocumentActive={returnTrue}
- addDocument={this.props.addDocument}
- moveDocument={this.props.moveDocument}
- addDocTab={this.props.addDocTab}
- pinToPres={emptyFunction}
- rootSelected={this.props.isSelected}
- removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={this.getTransform(dref)}
- PanelWidth={nested ? pair.layout[WidthSym] : this.dimension}
- PanelHeight={nested ? pair.layout[HeightSym] : this.dimension}
- renderDepth={this.props.renderDepth + 1}
- focus={emptyFunction}
- styleProvider={this.props.styleProvider}
- layerProvider={this.props.layerProvider}
- docViewPath={returnEmptyDoclist}
- whenChildContentsActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- docFilters={this.props.docFilters}
- docRangeFilters={this.props.docRangeFilters}
- searchFilterDocs={this.props.searchFilterDocs}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />
- </div>;
- })}
- </div>
- {DocumentLinksButton.StartLink ? <span className="bottomPopup-background" style={{
- pointerEvents: "all"
- }}
- onPointerDown={e => e.stopPropagation()} >
- <span className="bottomPopup-text" >
- Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
- </span>
-
- <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
- <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
- Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
- </span>
- </Tooltip>
-
- <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
- Stop
- </span>
- </Tooltip>
-
- </span> : null}
- </div>
- </div>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss
index f04b19ef7..c35f088a6 100644
--- a/src/client/views/collections/CollectionMenu.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,628 +1,659 @@
@import "../global/globalCssVariables";
-.collectionMenu-cont {
- position: relative;
- display: inline-flex;
- width: 100%;
- opacity: 0.9;
- z-index: 901;
- transition: top .5s;
- background: $dark-gray;
- color: $white;
- transform-origin: top left;
- top: 0;
- width: 100%;
-
- .recordButtonOutline {
- border-radius: 100%;
- width: 18px;
- height: 18px;
- border: solid 1px $white;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .recordButtonInner {
- border-radius: 100%;
- width: 70%;
- height: 70%;
- background: $white;
- }
-
- .collectionMenu {
- display: flex;
- height: 100%;
- overflow: visible;
- z-index: 901;
- border: unset;
-
- .collectionMenu-divider {
- height: 100%;
- margin-left: 3px;
- margin-right: 3px;
- width: 2px;
- background-color: $medium-gray;
- }
-
- .collectionViewBaseChrome {
- display: flex;
- align-items: center;
-
- .collectionViewBaseChrome-viewPicker {
- font-size: $small-text;
- outline-color: $black;
- color: $white;
- border: none;
- background: $dark-gray;
- }
-
- .collectionViewBaseChrome-viewPicker:focus {
- outline: none;
- border: none;
- }
-
- .collectionViewBaseChrome-viewPicker:active {
- outline-color: $black;
- }
-
- .collectionViewBaseChrome-button {
- font-size: $small-text;
- text-transform: uppercase;
- letter-spacing: 2px;
- background: $white;
- color: $pink;
- outline-color: $black;
- border: none;
- padding: 12px 10px 11px 10px;
- margin-left: 10px;
- }
-
- .collectionViewBaseChrome-cmdPicker {
- margin-left: 3px;
- margin-right: 0px;
- font-size: $small-text;
- text-transform: capitalize;
- color: $white;
- border: none;
- background: $dark-gray;
- }
-
- .collectionViewBaseChrome-cmdPicker:focus {
- border: none;
- outline: none;
- }
-
- .commandEntry-outerDiv {
- pointer-events: all;
- background-color: transparent;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- height: 100%;
- overflow: hidden;
-
- .commandEntry-drop {
- color: $white;
- width: 30px;
- margin-top: auto;
- margin-bottom: auto;
- }
- }
-
- .commandEntry-outerDiv:hover{
- background-color: $drop-shadow;
-
- .collectionViewBaseChrome-viewPicker,
- .collectionViewBaseChrome-cmdPicker{
- background: $dark-gray;
- }
- }
-
- .collectionViewBaseChrome-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 30px;
- transform-origin: top left;
- pointer-events: all;
- // margin-top: 10px;
- }
-
- @media only screen and (max-device-width: 480px) {
- .collectionViewBaseChrome-collapse {
- display: none;
- }
- }
-
- .collectionViewBaseChrome-template,
- .collectionViewBaseChrome-viewModes {
- align-items: center;
- height: 100%;
- display: flex;
- background: transparent;
- color: $medium-gray;
- justify-content: center;
- }
-
- .collectionViewBaseChrome-viewSpecs {
- margin-left: 5px;
- display: grid;
- border: none;
- border-right: solid $medium-gray 1px;
-
- .collectionViewBaseChrome-filterIcon {
- position: relative;
- display: flex;
- margin: auto;
- background: $dark-gray;
- color: $white;
- width: 30px;
- height: 30px;
- align-items: center;
- justify-content: center;
- border: none;
- border-right: solid $medium-gray 1px;
- }
-
- .collectionViewBaseChrome-viewSpecsInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: $medium-gray;
- text-align: center;
- letter-spacing: 2px;
- outline-color: $black;
- font-size: $small-text;
- background: $white;
- height: 100%;
- width: 75px;
- }
-
- .collectionViewBaseChrome-viewSpecsMenu {
- overflow: hidden;
- transition: height .5s, display .5s;
- position: absolute;
- top: 60px;
- z-index: 100;
- display: flex;
- flex-direction: column;
- background: $white;
- box-shadow: $medium-gray 2px 2px 4px;
-
- .qs-datepicker {
- left: unset;
- right: 0;
- }
-
- .collectionViewBaseChrome-viewSpecsMenu-row {
- display: grid;
- grid-template-columns: 150px 200px 150px;
- margin-top: 10px;
- margin-right: 10px;
-
- .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
- .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
- .collectionViewBaseChrome-viewSpecsMenu-rowRight {
- font-size: $small-text;
- letter-spacing: 2px;
- color: $medium-gray;
- margin-left: 10px;
- padding: 5px;
- border: none;
- outline-color: $black;
- }
- }
-
- .collectionViewBaseChrome-viewSpecsMenu-lastRow {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
- grid-gap: 10px;
- margin: 10px;
- }
- }
- }
- }
-
- .collectionStackingViewChrome-cont,
- .collectionTreeViewChrome-cont,
- .collection3DCarouselViewChrome-cont {
- display: flex;
- justify-content: space-between;
- }
-
- .collectionGridViewChrome-cont {
- display: flex;
- margin-left: 10;
-
- .collectionGridViewChrome-viewPicker {
- font-size: $small-text;
- //text-transform: uppercase;
- //letter-spacing: 2px;
- background: $dark-gray;
- color: $white;
- outline-color: $black;
- color: $white;
- border: none;
- border-right: solid $medium-gray 1px;
- }
-
- .collectionGridViewChrome-viewPicker:active {
- outline-color: $black;
- }
-
- .grid-control {
- align-self: center;
- display: flex;
- flex-direction: row;
- margin-right: 5px;
-
- .grid-icon {
- margin-right: 5px;
- align-self: center;
- }
-
- .flexLabel {
- margin-bottom: 0;
- }
-
- .collectionGridViewChrome-entryBox {
- width: 50%;
- color: $black;
- }
-
- .collectionGridViewChrome-columnButton {
- color: $black;
- }
- }
- }
-
- .collectionStackingViewChrome-sort,
- .collectionTreeViewChrome-sort {
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- .collectionStackingViewChrome-sortIcon,
- .collectionTreeViewChrome-sortIcon {
- transition: transform .5s;
- margin-left: 10px;
- }
- }
-
- button:hover {
- transform: scale(1);
- }
-
-
- .collectionStackingViewChrome-pivotField-cont,
- .collectionTreeViewChrome-pivotField-cont,
- .collection3DCarouselViewChrome-scrollSpeed-cont {
- justify-self: right;
- align-items: center;
- display: flex;
- grid-auto-columns: auto;
- font-size: $small-text;
- letter-spacing: 2px;
-
- .collectionStackingViewChrome-pivotField-label,
- .collectionTreeViewChrome-pivotField-label,
- .collection3DCarouselViewChrome-scrollSpeed-label {
- grid-column: 1;
- margin-right: 7px;
- user-select: none;
- font-family: $sans-serif;
- letter-spacing: normal;
- }
-
- .collectionStackingViewChrome-sortIcon {
- transition: transform .5s;
- grid-column: 3;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- width: 25px;
- height: 25px;
- border-radius: 100%;
- }
-
- .collectionStackingViewChrome-sortIcon:hover {
- background-color: $drop-shadow;
- }
-
- .collectionStackingViewChrome-pivotField,
- .collectionTreeViewChrome-pivotField,
- .collection3DCarouselViewChrome-scrollSpeed {
- color: $white;
- grid-column: 2;
- grid-row: 1;
- width: 90%;
- min-width: 100px;
- display: flex;
- height: 80%;
- border-radius: 7px;
- align-items: center;
- background: $white;
-
- .editable-view-input,
- input,
- .editableView-container-editing-oneLine,
- .editableView-container-editing {
- margin: auto;
- border: 0px;
- color: $light-gray !important;
- text-align: center;
- letter-spacing: 2px;
- outline-color: $black;
- height: 100%;
- }
-
- .react-autosuggest__container {
- margin: 0;
- color: $medium-gray;
- padding: 0px;
- }
- }
- }
-
- .collectionStackingViewChrome-pivotField:hover,
- .collectionTreeViewChrome-pivotField:hover,
- .collection3DCarouselViewChrome-scrollSpeed:hover {
- cursor: text;
- }
-
- }
-}
-
-.collectionMenu-webUrlButtons {
- margin-left: 44;
- background: lightGray;
+.collectionMenu-container {
display: flex;
-}
-
-.webBox-urlEditor {
- position: relative;
- opacity: 0.9;
- z-index: 901;
- transition: top .5s;
-
- .urlEditor {
- display: grid;
- grid-template-columns: 1fr auto;
- padding-bottom: 10px;
- overflow: hidden;
- margin-top: 5px;
- height: 35px;
-
- .editorBase {
- display: flex;
-
- .editor-collapse {
- transition: all .5s, opacity 0.3s;
- position: absolute;
- width: 40px;
- transform-origin: top left;
- }
-
- .switchToText {
- color: $medium-gray;
- }
-
- .switchToText:hover {
- color: $dark-gray;
- }
- }
-
- button:hover {
- transform: scale(1);
- }
- }
-}
-
-.collectionMenu-urlInput {
- padding: 12px 10px 11px 10px;
- border: 0px;
- color: $black;
- font-size: $small-text;
- letter-spacing: 2px;
- outline-color: $black;
- background: $white;
- width: 100%;
- min-width: 350px;
- margin-right: 10px;
- height: 100%;
-}
-
-.collectionFreeFormMenu-cont {
- display: inline-flex;
position: relative;
+ align-content: center;
+ justify-content: space-between;
+ background-color: $dark-gray;
+ height: 35px;
+ border-bottom: $standard-border;
+ padding-right: 5px;
align-items: center;
- height: 100%;
-
- .color-previewI {
- width: 60%;
- top: 80%;
- position: absolute;
- height: 4px;
- }
-
- .color-previewII {
- width: 80%;
- height: 80%;
- margin-left: 10%;
- position: absolute;
- bottom: 5;
- }
-
- .btn-group {
- display: grid;
- grid-template-columns: auto auto auto auto;
- margin: auto;
- /* Make the buttons appear below each other */
- }
- .btn-draw {
- display: inline-flex;
- margin: auto;
- /* Make the buttons appear below each other */
- }
-
- .fwdKeyframe,
- .numKeyframe,
- .backKeyframe {
+ .collectionMenu-hardCodedButton {
cursor: pointer;
- position: relative;
- width: 20;
- height: 30;
- bottom: 0;
- background: $dark-gray;
- display: inline-flex;
- align-items: center;
color: $white;
- }
-
- .backKeyframe {
- svg {
- display: block;
- margin: auto;
- }
- }
-
-
- .numKeyframe {
- flex-direction: column;
- padding-top: 5px;
- }
-
- .fwdKeyframe {
- svg {
- display: block;
- margin: auto;
- }
-
- border-right: solid $medium-gray 1px;
- }
-}
-
-.collectionSchemaViewChrome-cont {
- display: flex;
- font-size: $small-text;
-
- .collectionSchemaViewChrome-toggle {
- display: flex;
- margin-left: 10px;
- }
-
- .collectionSchemaViewChrome-label {
- text-transform: uppercase;
- letter-spacing: 2px;
- margin-right: 5px;
+ width: 25px;
+ height: 25px;
+ padding: 5;
+ text-align: center;
display: flex;
- flex-direction: column;
justify-content: center;
- }
-
- .collectionSchemaViewChrome-toggler {
- width: 100px;
- height: 35px;
- background-color: $black;
+ align-items: center;
position: relative;
- }
-
- .collectionSchemaViewChrome-togglerButton {
- width: 47px;
- height: 30px;
- background-color: $light-gray;
- // position: absolute;
- transition: all 0.5s ease;
- // top: 3px;
- margin-top: 3px;
- color: $medium-gray;
- letter-spacing: 2px;
- text-transform: uppercase;
- display: flex;
- flex-direction: column;
- justify-content: center;
- text-align: center;
+ transition: 0.2s;
+ border-radius: 3px;
- &.on {
- margin-left: 3px;
- }
-
- &.off {
- margin-left: 50px;
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.2);
}
}
}
-
-.commandEntry-outerDiv {
- display: flex;
- flex-direction: column;
- height: 40px;
-}
-
-.commandEntry-inputArea {
- display: flex;
- flex-direction: row;
- width: 150px;
- margin: auto auto auto auto;
-}
-
-.react-autosuggest__container {
- position: relative;
- width: 100%;
- margin-left: 5px;
- margin-right: 5px;
-}
-
-.react-autosuggest__input {
- border: 1px solid $light-gray;
- border-radius: 4px;
- width: 100%;
-}
-
-.react-autosuggest__input--focused {
- outline: none;
-}
-
-.react-autosuggest__input--open {
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-.react-autosuggest__suggestions-container {
- display: none;
-}
-
-.react-autosuggest__suggestions-container--open {
- display: block;
- position: fixed;
- overflow-y: auto;
- max-height: 400px;
- width: 180px;
- border: 1px solid $light-gray;
- background-color: $white;
- font-family: $sans-serif;
- font-weight: 300;
- font-size: $large-header;
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- z-index: 2;
-}
-
-.react-autosuggest__suggestions-list {
- margin: 0;
- padding: 0;
- list-style-type: none;
-}
-
-.react-autosuggest__suggestion {
- cursor: pointer;
- padding: 10px 20px;
-}
-
-.react-autosuggest__suggestion--highlighted {
- background-color: $light-gray;
-} \ No newline at end of file
+// .collectionMenu-cont {
+// position: relative;
+// display: inline-flex;
+// width: 100%;
+// opacity: 0.9;
+// z-index: 901;
+// transition: top .5s;
+// background: $dark-gray;
+// color: $white;
+// transform-origin: top left;
+// top: 0;
+// width: 100%;
+
+// .recordButtonOutline {
+// border-radius: 100%;
+// width: 18px;
+// height: 18px;
+// border: solid 1px $white;
+// display: flex;
+// align-items: center;
+// justify-content: center;
+// }
+
+// .recordButtonInner {
+// border-radius: 100%;
+// width: 70%;
+// height: 70%;
+// background: $white;
+// }
+
+// .collectionMenu {
+// display: flex;
+// height: 100%;
+// overflow: visible;
+// z-index: 901;
+// border: unset;
+
+// .collectionMenu-divider {
+// height: 100%;
+// margin-left: 3px;
+// margin-right: 3px;
+// width: 2px;
+// background-color: $medium-gray;
+// }
+
+// .collectionViewBaseChrome {
+// display: flex;
+// align-items: center;
+
+// .collectionViewBaseChrome-viewPicker {
+// font-size: $small-text;
+// outline-color: $black;
+// color: $white;
+// border: none;
+// background: $dark-gray;
+// }
+
+// .collectionViewBaseChrome-viewPicker:focus {
+// outline: none;
+// border: none;
+// }
+
+// .collectionViewBaseChrome-viewPicker:active {
+// outline-color: $black;
+// }
+
+// .collectionViewBaseChrome-button {
+// font-size: $small-text;
+// text-transform: uppercase;
+// letter-spacing: 2px;
+// background: $white;
+// color: $pink;
+// outline-color: $black;
+// border: none;
+// padding: 12px 10px 11px 10px;
+// margin-left: 10px;
+// }
+
+// .collectionViewBaseChrome-cmdPicker {
+// margin-left: 3px;
+// margin-right: 0px;
+// font-size: $small-text;
+// text-transform: capitalize;
+// color: $white;
+// border: none;
+// background: $dark-gray;
+// }
+
+// .collectionViewBaseChrome-cmdPicker:focus {
+// border: none;
+// outline: none;
+// }
+
+// .commandEntry-outerDiv {
+// pointer-events: all;
+// background-color: transparent;
+// display: flex;
+// flex-direction: row;
+// align-items: center;
+// justify-content: center;
+// height: 100%;
+// overflow: hidden;
+
+// .commandEntry-drop {
+// color: $white;
+// width: 30px;
+// margin-top: auto;
+// margin-bottom: auto;
+// }
+// }
+
+// .commandEntry-outerDiv:hover{
+// background-color: $drop-shadow;
+
+// .collectionViewBaseChrome-viewPicker,
+// .collectionViewBaseChrome-cmdPicker{
+// background: $dark-gray;
+// }
+// }
+
+// .collectionViewBaseChrome-collapse {
+// transition: all .5s, opacity 0.3s;
+// position: absolute;
+// width: 30px;
+// transform-origin: top left;
+// pointer-events: all;
+// // margin-top: 10px;
+// }
+
+// @media only screen and (max-device-width: 480px) {
+// .collectionViewBaseChrome-collapse {
+// display: none;
+// }
+// }
+
+// .collectionViewBaseChrome-template,
+// .collectionViewBaseChrome-viewModes {
+// align-items: center;
+// height: 100%;
+// display: flex;
+// background: transparent;
+// color: $medium-gray;
+// justify-content: center;
+// }
+
+// .collectionViewBaseChrome-viewSpecs {
+// margin-left: 5px;
+// display: grid;
+// border: none;
+// border-right: solid $medium-gray 1px;
+
+// .collectionViewBaseChrome-filterIcon {
+// position: relative;
+// display: flex;
+// margin: auto;
+// background: $dark-gray;
+// color: $white;
+// width: 30px;
+// height: 30px;
+// align-items: center;
+// justify-content: center;
+// border: none;
+// border-right: solid $medium-gray 1px;
+// }
+
+// .collectionViewBaseChrome-viewSpecsInput {
+// padding: 12px 10px 11px 10px;
+// border: 0px;
+// color: $medium-gray;
+// text-align: center;
+// letter-spacing: 2px;
+// outline-color: $black;
+// font-size: $small-text;
+// background: $white;
+// height: 100%;
+// width: 75px;
+// }
+
+// .collectionViewBaseChrome-viewSpecsMenu {
+// overflow: hidden;
+// transition: height .5s, display .5s;
+// position: absolute;
+// top: 60px;
+// z-index: 100;
+// display: flex;
+// flex-direction: column;
+// background: $white;
+// box-shadow: $medium-gray 2px 2px 4px;
+
+// .qs-datepicker {
+// left: unset;
+// right: 0;
+// }
+
+// .collectionViewBaseChrome-viewSpecsMenu-row {
+// display: grid;
+// grid-template-columns: 150px 200px 150px;
+// margin-top: 10px;
+// margin-right: 10px;
+
+// .collectionViewBaseChrome-viewSpecsMenu-rowLeft,
+// .collectionViewBaseChrome-viewSpecsMenu-rowMiddle,
+// .collectionViewBaseChrome-viewSpecsMenu-rowRight {
+// font-size: $small-text;
+// letter-spacing: 2px;
+// color: $medium-gray;
+// margin-left: 10px;
+// padding: 5px;
+// border: none;
+// outline-color: $black;
+// }
+// }
+
+// .collectionViewBaseChrome-viewSpecsMenu-lastRow {
+// display: grid;
+// grid-template-columns: 1fr 1fr 1fr;
+// grid-gap: 10px;
+// margin: 10px;
+// }
+// }
+// }
+// }
+
+// .collectionStackingViewChrome-cont,
+// .collectionTreeViewChrome-cont,
+// .collection3DCarouselViewChrome-cont {
+// display: flex;
+// justify-content: space-between;
+// }
+
+// .collectionGridViewChrome-cont {
+// display: flex;
+// margin-left: 10;
+
+// .collectionGridViewChrome-viewPicker {
+// font-size: $small-text;
+// //text-transform: uppercase;
+// //letter-spacing: 2px;
+// background: $dark-gray;
+// color: $white;
+// outline-color: $black;
+// color: $white;
+// border: none;
+// border-right: solid $medium-gray 1px;
+// }
+
+// .collectionGridViewChrome-viewPicker:active {
+// outline-color: $black;
+// }
+
+// .grid-control {
+// align-self: center;
+// display: flex;
+// flex-direction: row;
+// margin-right: 5px;
+
+// .grid-icon {
+// margin-right: 5px;
+// align-self: center;
+// }
+
+// .flexLabel {
+// margin-bottom: 0;
+// }
+
+// .collectionGridViewChrome-entryBox {
+// width: 50%;
+// color: $black;
+// }
+
+// .collectionGridViewChrome-columnButton {
+// color: $black;
+// }
+// }
+// }
+
+// .collectionStackingViewChrome-sort,
+// .collectionTreeViewChrome-sort {
+// display: flex;
+// align-items: center;
+// justify-content: space-between;
+
+// .collectionStackingViewChrome-sortIcon,
+// .collectionTreeViewChrome-sortIcon {
+// transition: transform .5s;
+// margin-left: 10px;
+// }
+// }
+
+// button:hover {
+// transform: scale(1);
+// }
+
+
+// .collectionStackingViewChrome-pivotField-cont,
+// .collectionTreeViewChrome-pivotField-cont,
+// .collection3DCarouselViewChrome-scrollSpeed-cont {
+// justify-self: right;
+// align-items: center;
+// display: flex;
+// grid-auto-columns: auto;
+// font-size: $small-text;
+// letter-spacing: 2px;
+
+// .collectionStackingViewChrome-pivotField-label,
+// .collectionTreeViewChrome-pivotField-label,
+// .collection3DCarouselViewChrome-scrollSpeed-label {
+// grid-column: 1;
+// margin-right: 7px;
+// user-select: none;
+// font-family: $sans-serif;
+// letter-spacing: normal;
+// }
+
+// .collectionStackingViewChrome-sortIcon {
+// transition: transform .5s;
+// grid-column: 3;
+// text-align: center;
+// display: flex;
+// justify-content: center;
+// align-items: center;
+// cursor: pointer;
+// width: 25px;
+// height: 25px;
+// border-radius: 100%;
+// }
+
+// .collectionStackingViewChrome-sortIcon:hover {
+// background-color: $drop-shadow;
+// }
+
+// .collectionStackingViewChrome-pivotField,
+// .collectionTreeViewChrome-pivotField,
+// .collection3DCarouselViewChrome-scrollSpeed {
+// color: $white;
+// grid-column: 2;
+// grid-row: 1;
+// width: 90%;
+// min-width: 100px;
+// display: flex;
+// height: 80%;
+// border-radius: 7px;
+// align-items: center;
+// background: $white;
+
+// .editable-view-input,
+// input,
+// .editableView-container-editing-oneLine,
+// .editableView-container-editing {
+// margin: auto;
+// border: 0px;
+// color: $light-gray !important;
+// text-align: center;
+// letter-spacing: 2px;
+// outline-color: $black;
+// height: 100%;
+// }
+
+// .react-autosuggest__container {
+// margin: 0;
+// color: $medium-gray;
+// padding: 0px;
+// }
+// }
+// }
+
+// .collectionStackingViewChrome-pivotField:hover,
+// .collectionTreeViewChrome-pivotField:hover,
+// .collection3DCarouselViewChrome-scrollSpeed:hover {
+// cursor: text;
+// }
+
+// }
+// }
+
+// .collectionMenu-webUrlButtons {
+// margin-left: 44;
+// background: lightGray;
+// display: flex;
+// }
+
+// .webBox-urlEditor {
+// position: relative;
+// opacity: 0.9;
+// z-index: 901;
+// transition: top .5s;
+
+// .urlEditor {
+// display: grid;
+// grid-template-columns: 1fr auto;
+// padding-bottom: 10px;
+// overflow: hidden;
+// margin-top: 5px;
+// height: 35px;
+
+// .editorBase {
+// display: flex;
+
+// .editor-collapse {
+// transition: all .5s, opacity 0.3s;
+// position: absolute;
+// width: 40px;
+// transform-origin: top left;
+// }
+
+// .switchToText {
+// color: $medium-gray;
+// }
+
+// .switchToText:hover {
+// color: $dark-gray;
+// }
+// }
+
+// button:hover {
+// transform: scale(1);
+// }
+// }
+// }
+
+// .collectionMenu-urlInput {
+// padding: 12px 10px 11px 10px;
+// border: 0px;
+// color: $black;
+// font-size: $small-text;
+// letter-spacing: 2px;
+// outline-color: $black;
+// background: $white;
+// width: 100%;
+// min-width: 350px;
+// margin-right: 10px;
+// height: 100%;
+// }
+
+// .collectionFreeFormMenu-cont {
+// display: inline-flex;
+// position: relative;
+// align-items: center;
+// height: 100%;
+
+// .color-previewI {
+// width: 60%;
+// top: 80%;
+// position: absolute;
+// height: 4px;
+// }
+
+// .color-previewII {
+// width: 80%;
+// height: 80%;
+// margin-left: 10%;
+// position: absolute;
+// bottom: 5;
+// }
+
+// .btn-group {
+// display: grid;
+// grid-template-columns: auto auto auto auto;
+// margin: auto;
+// /* Make the buttons appear below each other */
+// }
+
+// .btn-draw {
+// display: inline-flex;
+// margin: auto;
+// /* Make the buttons appear below each other */
+// }
+
+// .fwdKeyframe,
+// .numKeyframe,
+// .backKeyframe {
+// cursor: pointer;
+// position: relative;
+// width: 20;
+// height: 30;
+// bottom: 0;
+// background: $dark-gray;
+// display: inline-flex;
+// align-items: center;
+// color: $white;
+// }
+
+// .backKeyframe {
+// svg {
+// display: block;
+// margin: auto;
+// }
+// }
+
+
+// .numKeyframe {
+// flex-direction: column;
+// padding-top: 5px;
+// }
+
+// .fwdKeyframe {
+// svg {
+// display: block;
+// margin: auto;
+// }
+
+// border-right: solid $medium-gray 1px;
+// }
+// }
+
+// .collectionSchemaViewChrome-cont {
+// display: flex;
+// font-size: $small-text;
+
+// .collectionSchemaViewChrome-toggle {
+// display: flex;
+// margin-left: 10px;
+// }
+
+// .collectionSchemaViewChrome-label {
+// text-transform: uppercase;
+// letter-spacing: 2px;
+// margin-right: 5px;
+// display: flex;
+// flex-direction: column;
+// justify-content: center;
+// }
+
+// .collectionSchemaViewChrome-toggler {
+// width: 100px;
+// height: 35px;
+// background-color: $black;
+// position: relative;
+// }
+
+// .collectionSchemaViewChrome-togglerButton {
+// width: 47px;
+// height: 30px;
+// background-color: $light-gray;
+// // position: absolute;
+// transition: all 0.5s ease;
+// // top: 3px;
+// margin-top: 3px;
+// color: $medium-gray;
+// letter-spacing: 2px;
+// text-transform: uppercase;
+// display: flex;
+// flex-direction: column;
+// justify-content: center;
+// text-align: center;
+
+// &.on {
+// margin-left: 3px;
+// }
+
+// &.off {
+// margin-left: 50px;
+// }
+// }
+// }
+
+
+// .commandEntry-outerDiv {
+// display: flex;
+// flex-direction: column;
+// height: 40px;
+// }
+
+// .commandEntry-inputArea {
+// display: flex;
+// flex-direction: row;
+// width: 150px;
+// margin: auto auto auto auto;
+// }
+
+// .react-autosuggest__container {
+// position: relative;
+// width: 100%;
+// margin-left: 5px;
+// margin-right: 5px;
+// }
+
+// .react-autosuggest__input {
+// border: 1px solid $light-gray;
+// border-radius: 4px;
+// width: 100%;
+// }
+
+// .react-autosuggest__input--focused {
+// outline: none;
+// }
+
+// .react-autosuggest__input--open {
+// border-bottom-left-radius: 0;
+// border-bottom-right-radius: 0;
+// }
+
+// .react-autosuggest__suggestions-container {
+// display: none;
+// }
+
+// .react-autosuggest__suggestions-container--open {
+// display: block;
+// position: fixed;
+// overflow-y: auto;
+// max-height: 400px;
+// width: 180px;
+// border: 1px solid $light-gray;
+// background-color: $white;
+// font-family: $sans-serif;
+// font-weight: 300;
+// font-size: $large-header;
+// border-bottom-left-radius: 4px;
+// border-bottom-right-radius: 4px;
+// z-index: 2;
+// }
+
+// .react-autosuggest__suggestions-list {
+// margin: 0;
+// padding: 0;
+// list-style-type: none;
+// }
+
+// .react-autosuggest__suggestion {
+// cursor: pointer;
+// padding: 10px 20px;
+// }
+
+// .react-autosuggest__suggestion--highlighted {
+// background-color: $light-gray;
+// } \ No newline at end of file
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8f4df4a92..2c2d5dc75 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
-import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import { ColorState } from "react-color";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
@@ -15,37 +15,47 @@ import { RichTextField } from "../../../fields/RichTextField";
import { listSpec } from "../../../fields/Schema";
import { ScriptField } from "../../../fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils";
+import { Docs } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
import { DragManager } from "../../util/DragManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu";
import { EditableView } from "../EditableView";
import { GestureOverlay } from "../GestureOverlay";
-import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke";
+import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke";
+import { LightboxView } from "../LightboxView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
+import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
import { PresBox } from "../nodes/trails/PresBox";
+import { DefaultStyleProvider } from "../StyleProvider";
+import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionLinearView } from "./collectionLinear";
import "./CollectionMenu.scss";
import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { TabDocView } from "./TabDocView";
-import { LightboxView } from "../LightboxView";
-import { Docs } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
-import { CollectionDockingView } from "./CollectionDockingView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
+import { Colors } from "../global/globalEnums";
+
+interface CollectionMenuProps {
+ panelHeight: () => number;
+ panelWidth: () => number;
+}
@observer
-export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
+export class CollectionMenu extends AntimodeMenu<CollectionMenuProps>{
@observable static Instance: CollectionMenu;
@observable SelectedCollection: DocumentView | undefined;
@observable FieldKey: string;
+ private _docBtnRef = React.createRef<HTMLDivElement>();
+
constructor(props: any) {
super(props);
this.FieldKey = "";
@@ -57,7 +67,7 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
componentDidMount() {
reaction(() => SelectionManager.Views().length && SelectionManager.Views()[0],
- (doc) => doc && this.SetSelection(doc));
+ view => view && this.SetSelection(view));
}
@action
@@ -82,34 +92,92 @@ export class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> {
}
}
+ buttonBarXf = () => {
+ if (!this._docBtnRef.current) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current);
+ return new Transform(-translateX, -translateY, 1 / scale);
+ }
+
+ @computed get contMenuButtons() {
+ const selDoc = Doc.UserDoc().contextMenuBtns;
+ return !(selDoc instanceof Doc) ? (null) : <div className="collectionMenu-contMenuButtons" ref={this._docBtnRef} style={{ height: this.props.panelHeight() }} >
+ <CollectionLinearView
+ Document={selDoc}
+ DataDoc={undefined}
+ fieldKey={"data"}
+ dropAction={"alias"}
+ setHeight={returnFalse}
+ styleProvider={DefaultStyleProvider}
+ layerProvider={undefined}
+ rootSelected={returnTrue}
+ bringToFront={emptyFunction}
+ select={emptyFunction}
+ isContentActive={returnTrue}
+ isAnyChildContentActive={returnFalse}
+ isSelected={returnFalse}
+ docViewPath={returnEmptyDoclist}
+ moveDocument={returnFalse}
+ CollectionView={undefined}
+ addDocument={returnFalse}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={this.buttonBarXf}
+ PanelWidth={this.props.panelWidth}
+ PanelHeight={this.props.panelHeight}
+ renderDepth={0}
+ focus={emptyFunction}
+ whenChildContentsActiveChanged={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ </div>;
+ }
+
render() {
- const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
- <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
- <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- </button>
- </Tooltip>;
const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left";
const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel";
const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom">
- <button className="antimodeMenu-button" key="properties" style={{ backgroundColor: "#424242" }}
+ <div className="collectionMenu-hardCodedButton"
+ style={{ backgroundColor: CurrentUserUtils.propertiesWidth > 0 ? Colors.MEDIUM_BLUE : undefined }}
+ key="properties"
onPointerDown={this.toggleProperties}>
<FontAwesomeIcon icon={propIcon} size="lg" />
- </button>
+ </div>
</Tooltip>;
- return this.getElement(!this.SelectedCollection ? [/*button*/] :
- [<CollectionViewBaseChrome key="chrome"
- docView={this.SelectedCollection}
- fieldKey={this.SelectedCollection.LayoutFieldKey}
- type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
- prop,
- /*button*/]);
+ // NEW BUTTONS
+ //dash col linear view buttons
+ const contMenuButtons =
+ <div className="collectionMenu-container">
+ {this.contMenuButtons}
+ {prop}
+ </div>;
+
+ return contMenuButtons;
+
+ // const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom">
+ // <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}>
+ // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
+ // </button>
+ // </Tooltip>;
+
+ // OLD BUTTONS
+ // return this.getElement(!this.SelectedCollection ? [/*button*/] :
+ // [<CollectionViewBaseChrome key="chrome"
+ // docView={this.SelectedCollection}
+ // fieldKey={this.SelectedCollection.LayoutFieldKey}
+ // type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />,
+ // prop,
+ // /*button*/]);
}
}
-interface CollectionMenuProps {
+interface CollectionViewMenuProps {
type: CollectionViewType;
fieldKey: string;
docView: DocumentView;
@@ -118,7 +186,7 @@ interface CollectionMenuProps {
const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@observer
-export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
+export class CollectionViewBaseChrome extends React.Component<CollectionViewMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get document() { return this.props.docView?.props.Document; }
@@ -374,10 +442,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
</div>);
}
- @computed get selectedDocumentView() {
- return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
- }
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
+ @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
@computed get notACollection() {
if (this.selectedDoc) {
const layoutField = Doc.LayoutField(this.selectedDoc);
@@ -485,8 +551,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
@undoBatch
onAliasButtonMoved = (e: PointerEvent) => {
const contentDiv = this.selectedDocumentView?.ContentDiv;
- if (contentDiv) {
- const dragData = new DragManager.DocumentDragData([this.selectedDocumentView!.props.Document]);
+ if (contentDiv && this.selectedDoc) {
+ const dragData = new DragManager.DocumentDragData([this.selectedDoc]);
const offset = [e.clientX - contentDiv.getBoundingClientRect().x, e.clientY - contentDiv.getBoundingClientRect().y];
dragData.defaultDropAction = "alias";
dragData.canEmbed = true;
@@ -563,14 +629,14 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
}
@observer
-export class CollectionDockingChrome extends React.Component<CollectionMenuProps> {
+export class CollectionDockingChrome extends React.Component<CollectionViewMenuProps> {
render() {
return (null);
}
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean, isDoc?: boolean }> {
+export class CollectionFreeFormViewChrome extends React.Component<CollectionViewMenuProps & { isOverlay: boolean, isDoc?: boolean }> {
public static Instance: CollectionFreeFormViewChrome;
constructor(props: any) {
super(props);
@@ -581,11 +647,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")];
}
@computed get childDocs() { return DocListCast(this.dataField); }
- @computed get selectedDocumentView() { return SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; }
- @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
- @computed get isText() {
- return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false;
- }
+ @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); }
+ @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); }
+ @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; }
@undoBatch
@action
@@ -614,12 +678,12 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
private _width = ["1", "5", "10", "100"];
private _dotsize = [10, 20, 30, 40];
- private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"];
- private _head = ["", "", "", "arrow", "", ""];
- private _end = ["", "", "arrow", "arrow", "", ""];
- private _shapePrims = ["", "line", "line", "line", "rectangle", "circle"];
- private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",];
- private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
+ private _draw = ["∿", "=", "⎯", "→", "↔︎", "ロ", "O"];
+ private _head = ["", "", "", "", "arrow", "", ""];
+ private _end = ["", "", "", "arrow", "arrow", "", ""];
+ private _shapePrims = ["", "", "line", "line", "line", "rectangle", "circle"];
+ private _title = ["pen", "highlighter", "line", "line with arrow", "line with double arrows", "square", "circle"];
+ private _faName = ["pen-fancy", "highlighter", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"];
@observable _selectedPrimitive = this._shapePrims.length;
@observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode
@observable _colorBtn = false;
@@ -629,6 +693,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; }
@action primCreated() {
if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke=
+ if (CurrentUserUtils.SelectedTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor);
CurrentUserUtils.SelectedTool = InkTool.None;
this._selectedPrimitive = this._shapePrims.length;
SetActiveArrowStart("none");
@@ -669,7 +734,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
this._keepPrimitiveMode = keep;
if (this._selectedPrimitive !== i) {
this._selectedPrimitive = i;
- CurrentUserUtils.SelectedTool = InkTool.Pen;
+ if (this._title[i] === "highlighter") {
+ CurrentUserUtils.SelectedTool = InkTool.Highlighter;
+ GestureOverlay.Instance.SavedColor = ActiveInkColor();
+ SetActiveInkColor("rgba(245, 230, 95, 0.75)");
+ } else {
+ CurrentUserUtils.SelectedTool = InkTool.Pen;
+ }
SetActiveArrowStart(this._head[i]);
SetActiveArrowEnd(this._end[i]);
SetActiveBezierApprox("300");
@@ -720,7 +791,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}>
- </button>
+ </button>
</Tooltip>)}
</div>;
}
@@ -759,7 +830,6 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</div>;
}
- @observable viewType = this.selectedDoc?._viewType;
render() {
return !this.props.docView.layoutDoc ? (null) :
@@ -797,7 +867,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
}
}
@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionStackingViewChrome extends React.Component<CollectionViewMenuProps> {
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
@@ -918,7 +988,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu
@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> {
// private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
get document() { return this.props.docView.props.Document; }
@@ -966,7 +1036,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionMenuPr
}
@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionTreeViewChrome extends React.Component<CollectionViewMenuProps> {
get document() { return this.props.docView.props.Document; }
get sortAscending() {
@@ -991,7 +1061,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionMenuProp
<button className="collectionTreeViewChrome-sort" onClick={this.toggleSort}>
<div className="collectionTreeViewChrome-sortLabel">
Sort
- </div>
+ </div>
<div className="collectionTreeViewChrome-sortIcon" style={{ transform: `rotate(${this.ascending === undefined ? "90" : this.ascending ? "180" : "0"}deg)` }}>
<FontAwesomeIcon icon="caret-up" size="2x" color="white" />
</div>
@@ -1003,7 +1073,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionMenuProp
// Enter scroll speed for 3D Carousel
@observer
-export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
+export class Collection3DCarouselViewChrome extends React.Component<CollectionViewMenuProps> {
get document() { return this.props.docView.props.Document; }
@computed get scrollSpeed() {
return this.document._autoScrollSpeed;
@@ -1044,7 +1114,7 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionMe
* Chrome for grid view.
*/
@observer
-export class CollectionGridViewChrome extends React.Component<CollectionMenuProps> {
+export class CollectionGridViewChrome extends React.Component<CollectionViewMenuProps> {
private clicked: boolean = false;
private entered: boolean = false;
@@ -1225,3 +1295,4 @@ Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) {
CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0);
doc._currentFrame = newFrame === undefined ? 0 : Math.max(0, newFrame);
});
+
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
deleted file mode 100644
index 6bdeaf722..000000000
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ /dev/null
@@ -1,575 +0,0 @@
-import React = require("react");
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from "mobx";
-import { observer } from "mobx-react";
-import Measure from "react-measure";
-import { Resize } from "react-table";
-import "react-table/react-table.css";
-import { Doc, Opt } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField";
-import { Cast, NumCast } from "../../../fields/Types";
-import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils";
-import { DocUtils } from "../../documents/Documents";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { COLLECTION_BORDER_WIDTH, SCHEMA_DIVIDER_WIDTH } from '../../views/global/globalCssVariables.scss';
-import { SchemaTable } from "../collections/collectionSchema/SchemaTable";
-import { ContextMenu } from "../ContextMenu";
-import { ContextMenuProps } from "../ContextMenuItem";
-import '../DocumentDecorations.scss';
-import { DocumentView } from "../nodes/DocumentView";
-import { DefaultStyleProvider } from "../StyleProvider";
-import "./CollectionSchemaView.scss";
-import { CollectionSubView } from "./CollectionSubView";
-// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
-
-export enum ColumnType {
- Any,
- Number,
- String,
- Boolean,
- Doc,
- Image,
- List,
- Date
-}
-// this map should be used for keys that should have a const type of value
-const columnTypes: Map<string, ColumnType> = new Map([
- ["title", ColumnType.String],
- ["x", ColumnType.Number], ["y", ColumnType.Number], ["_width", ColumnType.Number], ["_height", ColumnType.Number],
- ["_nativeWidth", ColumnType.Number], ["_nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
- ["_curPage", ColumnType.Number], ["_currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number]
-]);
-
-@observer
-export class CollectionSchemaView extends CollectionSubView(doc => doc) {
- private _previewCont?: HTMLDivElement;
-
- @observable _previewDoc: Doc | undefined = undefined;
- @observable _focusedTable: Doc = this.props.Document;
- @observable _col: any = "";
- @observable _menuWidth = 0;
- @observable _headerOpen = false;
- @observable _headerIsEditing = false;
- @observable _menuHeight = 0;
- @observable _pointerX = 0;
- @observable _pointerY = 0;
- @observable _openTypes: boolean = false;
-
- @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
- @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
- @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - Number(SCHEMA_DIVIDER_WIDTH) - this.previewWidth(); }
- @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
- @computed get scale() { return this.props.ScreenToLocalTransform().Scale; }
- @computed get columns() { return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); }
- set columns(columns: SchemaHeaderField[]) { this.props.Document._schemaHeaders = new List<SchemaHeaderField>(columns); }
-
- @computed get menuCoordinates() {
- let searchx = 0;
- let searchy = 0;
- if (this.props.Document._searchDoc) {
- const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0];
- if (el !== undefined) {
- const rect = el.getBoundingClientRect();
- searchx = rect.x;
- searchy = rect.y;
- }
- }
- const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx;
- const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy;
- return this.props.ScreenToLocalTransform().transformPoint(x, y);
- }
-
- get documentKeys() {
- const docs = this.childDocs;
- const keys: { [key: string]: boolean } = {};
- // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
- // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
- // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
- // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
- // is displayed (unlikely) it won't show up until something else changes.
- //TODO Types
- untracked(() => docs.map(doc => Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => keys[key] = false))));
-
- this.columns.forEach(key => keys[key.heading] = true);
- return Array.from(Object.keys(keys));
- }
-
- @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing;
-
- @undoBatch
- setColumnType = action((columnField: SchemaHeaderField, type: ColumnType): void => {
- this._openTypes = false;
- if (columnTypes.get(columnField.heading)) return;
-
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setType(NumCast(type));
- columns[index] = columnField;
- this.columns = columns;
- }
- });
-
- @undoBatch
- setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
- const columns = this.columns;
- const index = columns.indexOf(columnField);
- if (index > -1) {
- columnField.setColor(color);
- columns[index] = columnField;
- this.columns = columns; // need to set the columns to trigger rerender
- }
- }
-
- @undoBatch
- @action
- setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
- const columns = this.columns;
- columns.forEach(col => col.setDesc(undefined));
-
- const index = columns.findIndex(c => c.heading === columnField.heading);
- const column = columns[index];
- column.setDesc(descending);
- columns[index] = column;
- this.columns = columns;
- }
-
- renderTypes = (col: any) => {
- if (columnTypes.get(col.heading)) return (null);
-
- const type = col.type;
-
- const anyType = <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Any)}>
- <FontAwesomeIcon icon={"align-justify"} size="sm" />
- Any
- </div>;
-
- const numType = <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Number)}>
- <FontAwesomeIcon icon={"hashtag"} size="sm" />
- Number
- </div>;
-
- const textType = <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.String)}>
- <FontAwesomeIcon icon={"font"} size="sm" />
- Text
- </div>;
-
- const boolType = <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Boolean)}>
- <FontAwesomeIcon icon={"check-square"} size="sm" />
- Checkbox
- </div>;
-
- const listType = <div className={"columnMenu-option" + (type === ColumnType.List ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.List)}>
- <FontAwesomeIcon icon={"list-ul"} size="sm" />
- List
- </div>;
-
- const docType = <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Doc)}>
- <FontAwesomeIcon icon={"file"} size="sm" />
- Document
- </div>;
-
- const imageType = <div className={"columnMenu-option" + (type === ColumnType.Image ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Image)}>
- <FontAwesomeIcon icon={"image"} size="sm" />
- Image
- </div>;
-
- const dateType = <div className={"columnMenu-option" + (type === ColumnType.Date ? " active" : "")} onClick={() => this.setColumnType(col, ColumnType.Date)}>
- <FontAwesomeIcon icon={"calendar"} size="sm" />
- Date
- </div>;
-
-
- const allColumnTypes = <div className="columnMenu-types">
- {anyType}
- {numType}
- {textType}
- {boolType}
- {listType}
- {docType}
- {imageType}
- {dateType}
- </div>;
-
- const justColType = type === ColumnType.Any ? anyType : type === ColumnType.Number ? numType :
- type === ColumnType.String ? textType : type === ColumnType.Boolean ? boolType :
- type === ColumnType.List ? listType : type === ColumnType.Doc ? docType :
- type === ColumnType.Date ? dateType : imageType;
-
- return (
- <div className="collectionSchema-headerMenu-group" onClick={action(() => this._openTypes = !this._openTypes)}>
- <div>
- <label style={{ cursor: "pointer" }}>Column type:</label>
- <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right", transform: `rotate(${this._openTypes ? "180deg" : 0})`, transition: "0.2s all ease" }} />
- </div>
- {this._openTypes ? allColumnTypes : justColType}
- </div >
- );
- }
-
- renderSorting = (col: any) => {
- const sort = col.desc;
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Sort by:</label>
- <div className="columnMenu-sort">
- <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.setColumnSort(col, true)}>
- <FontAwesomeIcon icon="sort-amount-down" size="sm" />
- Sort descending
- </div>
- <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.setColumnSort(col, false)}>
- <FontAwesomeIcon icon="sort-amount-up" size="sm" />
- Sort ascending
- </div>
- <div className="columnMenu-option" onClick={() => this.setColumnSort(col, undefined)}>
- <FontAwesomeIcon icon="times" size="sm" />
- Clear sorting
- </div>
- </div>
- </div>
- );
- }
-
- renderColors = (col: any) => {
- const selected = col.color;
-
- const pink = PastelSchemaPalette.get("pink2");
- const purple = PastelSchemaPalette.get("purple2");
- const blue = PastelSchemaPalette.get("bluegreen1");
- const yellow = PastelSchemaPalette.get("yellow4");
- const red = PastelSchemaPalette.get("red2");
- const gray = "#f1efeb";
-
- return (
- <div className="collectionSchema-headerMenu-group">
- <label>Color:</label>
- <div className="columnMenu-colors">
- <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.setColumnColor(col, pink!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.setColumnColor(col, purple!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.setColumnColor(col, blue!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.setColumnColor(col, yellow!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.setColumnColor(col, red!)}></div>
- <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.setColumnColor(col, gray)}></div>
- </div>
- </div>
- );
- }
-
- @undoBatch
- @action
- changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
- } else {
- if (addNew) {
- columns.push(new SchemaHeaderField(newKey, "f1efeb"));
- this.columns = columns;
- } else {
- const index = columns.map(c => c.heading).indexOf(oldKey);
- if (index > -1) {
- const column = columns[index];
- column.setHeading(newKey);
- columns[index] = column;
- this.columns = columns;
- if (filter) {
- Doc.setDocFilter(this.props.Document, newKey, filter, "match");
- }
- else {
- this.props.Document._docFilters = undefined;
- }
- }
- }
- }
- }
-
- @action
- openHeader = (col: any, screenx: number, screeny: number) => {
- this._col = col;
- this._headerOpen = true;
- this._pointerX = screenx;
- this._pointerY = screeny;
- }
-
- @action
- closeHeader = () => { this._headerOpen = false; }
-
- @undoBatch
- @action
- deleteColumn = (key: string) => {
- const columns = this.columns;
- if (columns === undefined) {
- this.columns = new List<SchemaHeaderField>([]);
- } else {
- const index = columns.map(c => c.heading).indexOf(key);
- if (index > -1) {
- columns.splice(index, 1);
- this.columns = columns;
- }
- }
- this.closeHeader();
- }
-
- getPreviewTransform = (): Transform => {
- return this.props.ScreenToLocalTransform().translate(- this.borderWidth - NumCast(COLLECTION_BORDER_WIDTH) - this.tableWidth, - this.borderWidth);
- }
-
- @action
- onHeaderClick = (e: React.PointerEvent) => {
- e.stopPropagation();
- }
-
- @action
- onWheel(e: React.WheelEvent) {
- const scale = this.props.ScreenToLocalTransform().Scale;
- this.props.isContentActive(true) && e.stopPropagation();
- }
-
- @computed get renderMenuContent() {
- TraceMobx();
- return <div className="collectionSchema-header-menuOptions">
- {this.renderTypes(this._col)}
- {this.renderColors(this._col)}
- <div className="collectionSchema-headerMenu-group">
- <button onClick={() => { this.deleteColumn(this._col.heading); }}
- >Delete Column</button>
- </div>
- </div>;
- }
-
- private createTarget = (ele: HTMLDivElement) => {
- this._previewCont = ele;
- super.CreateDropTarget(ele);
- }
-
- isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable;
-
- @action setFocused = (doc: Doc) => this._focusedTable = doc;
-
- @action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaView(this, doc);
- this._previewDoc = doc;
- }
-
- //toggles preview side-panel of schema
- @action
- toggleExpander = () => {
- this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
- }
-
- onDividerDown = (e: React.PointerEvent) => {
- setupMoveUpEvents(this, e, this.onDividerMove, emptyFunction, this.toggleExpander);
- }
- @action
- onDividerMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const nativeWidth = this._previewCont!.getBoundingClientRect();
- const minWidth = 40;
- const maxWidth = 1000;
- const movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
- const width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
- this.props.Document.schemaPreviewWidth = width;
- return false;
- }
-
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
- if (this.props.isSelected(true)) e.stopPropagation();
- else this.props.select(false);
- }
- }
-
- @computed
- get previewDocument(): Doc | undefined { return this._previewDoc; }
-
- @computed
- get dividerDragger() {
- return this.previewWidth() === 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} >
- <div className="collectionSchemaView-dividerDragger" />
- </div>;
- }
-
- @computed
- get previewPanel() {
- return <div ref={this.createTarget} style={{ width: `${this.previewWidth()}px` }}>
- {!this.previewDocument ? (null) :
- <DocumentView
- Document={this.previewDocument}
- DataDoc={undefined}
- fitContentsToDoc={returnTrue}
- freezeDimensions={true}
- dontCenter={"y"}
- focus={DocUtils.DefaultFocus}
- renderDepth={this.props.renderDepth}
- rootSelected={this.rootSelected}
- PanelWidth={this.previewWidth}
- PanelHeight={this.previewHeight}
- isContentActive={returnTrue}
- isDocumentActive={returnFalse}
- ScreenToLocalTransform={this.getPreviewTransform}
- docFilters={this.childDocFilters}
- docRangeFilters={this.childDocRangeFilters}
- searchFilterDocs={this.searchFilterDocs}
- styleProvider={DefaultStyleProvider}
- layerProvider={undefined}
- docViewPath={returnEmptyDoclist}
- ContainingCollectionDoc={this.props.CollectionView?.props.Document}
- ContainingCollectionView={this.props.CollectionView}
- moveDocument={this.props.moveDocument}
- addDocument={this.props.addDocument}
- removeDocument={this.props.removeDocument}
- whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- bringToFront={returnFalse}
- />}
- </div>;
- }
-
- @computed
- get schemaTable() {
- return <SchemaTable
- Document={this.props.Document}
- PanelHeight={this.props.PanelHeight}
- PanelWidth={this.props.PanelWidth}
- childDocs={this.childDocs}
- CollectionView={this.props.CollectionView}
- ContainingCollectionView={this.props.ContainingCollectionView}
- ContainingCollectionDoc={this.props.ContainingCollectionDoc}
- fieldKey={this.props.fieldKey}
- renderDepth={this.props.renderDepth}
- moveDocument={this.props.moveDocument}
- ScreenToLocalTransform={this.props.ScreenToLocalTransform}
- active={this.props.isContentActive}
- onDrop={this.onExternalDrop}
- addDocTab={this.props.addDocTab}
- pinToPres={this.props.pinToPres}
- isSelected={this.props.isSelected}
- isFocused={this.isFocused}
- setFocused={this.setFocused}
- setPreviewDoc={this.setPreviewDoc}
- deleteDocument={this.props.removeDocument}
- addDocument={this.props.addDocument}
- dataDoc={this.props.DataDoc}
- columns={this.columns}
- documentKeys={this.documentKeys}
- headerIsEditing={this._headerIsEditing}
- openHeader={this.openHeader}
- onClick={this.onTableClick}
- onPointerDown={emptyFunction}
- onResizedChange={this.onResizedChange}
- setColumns={this.setColumns}
- reorderColumns={this.reorderColumns}
- changeColumns={this.changeColumns}
- setHeaderIsEditing={this.setHeaderIsEditing}
- changeColumnSort={this.setColumnSort}
- />;
- }
-
- @computed
- public get schemaToolbar() {
- return <div className="collectionSchemaView-toolbar">
- <div className="collectionSchemaView-toolbar-item">
- <div id="preview-schema-checkbox-div">
- <input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} />
- Show Preview
- </div>
- </div>
- </div>;
- }
-
- onSpecificMenu = (e: React.MouseEvent) => {
- if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) {
- const cm = ContextMenu.Instance;
- const options = cm.findByDescription("Options...");
- const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
- optionItems.push({ description: "remove", event: () => this._previewDoc && this.props.removeDocument?.(this._previewDoc), icon: "trash" });
- !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" });
- cm.displayMenu(e.clientX, e.clientY);
- (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this.
- e.stopPropagation();
- }
- }
-
- @action
- onTableClick = (e: React.MouseEvent): void => {
- if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) {
- this.setPreviewDoc(undefined);
- } else {
- e.stopPropagation();
- }
- this.setFocused(this.props.Document);
- this.closeHeader();
- }
-
- onResizedChange = (newResized: Resize[], event: any) => {
- const columns = this.columns;
- newResized.forEach(resized => {
- const index = columns.findIndex(c => c.heading === resized.id);
- const column = columns[index];
- column.setWidth(resized.value);
- columns[index] = column;
- });
- this.columns = columns;
- }
-
- @action
- setColumns = (columns: SchemaHeaderField[]) => this.columns = columns
-
- @undoBatch
- reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
- const columns = [...columnsValues];
- const oldIndex = columns.indexOf(toMove);
- const relIndex = columns.indexOf(relativeTo);
- const newIndex = (oldIndex > relIndex && !before) ? relIndex + 1 : (oldIndex < relIndex && before) ? relIndex - 1 : relIndex;
-
- if (oldIndex === newIndex) return;
-
- columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.columns = columns;
- }
-
- onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation();
-
- render() {
- TraceMobx();
- if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0);
- const menuContent = this.renderMenuContent;
- const menu = <div className="collectionSchema-header-menu"
- onWheel={e => this.onZoomMenu(e)}
- onPointerDown={e => this.onHeaderClick(e)}
- style={{ transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)` }}>
- <Measure offset onResize={action((r: any) => {
- const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height);
- this._menuWidth = dim[0]; this._menuHeight = dim[1];
- })}>
- {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>}
- </Measure>
- </div>;
- return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")}
- style={{
- overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white",
- pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined,
- width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative",
- }} >
- <div className="collectionSchemaView-tableContainer"
- style={{ width: `calc(100% - ${this.previewWidth()}px)` }}
- onContextMenu={this.onSpecificMenu}
- onPointerDown={this.onPointerDown}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()}
- onDrop={e => this.onExternalDrop(e, {})}
- ref={this.createTarget}>
- {this.schemaTable}
- </div>
- {this.dividerDragger}
- {!this.previewWidth() ? (null) : this.previewPanel}
- {this._headerOpen && this.props.isContentActive() ? menu : null}
- </div>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index e456c0664..59c21210a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,70 +1,94 @@
+@import "../global/globalCssVariables.scss";
+
.collectionStackedTimeline {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ z-index: 1000;
+ overflow: hidden;
+ top: 0px;
+
+ .collectionStackedTimeline-trim-shade {
position: absolute;
- width: 100%;
height: 100%;
- border: gray solid 1px;
- border-radius: 3px;
- z-index: 1000;
- overflow: hidden;
- top: 0px;
+ background-color: $dark-gray;
+ opacity: 0.3;
+ }
- .collectionStackedTimeline-selector {
- position: absolute;
- width: 10px;
- top: 2.5%;
- height: 95%;
- background: lightblue;
- border-radius: 5px;
- opacity: 0.3;
- z-index: 500;
- border-style: solid;
- border-color: darkblue;
- border-width: 1px;
- }
+ .collectionStackedTimeline-trim-controls {
+ height: 100%;
+ position: absolute;
+ box-sizing: border-box;
+ border: 2px solid $medium-blue;
+ display: flex;
+ justify-content: space-between;
+ max-width: 100%;
- .collectionStackedTimeline-current {
- width: 1px;
- height: 100%;
- background-color: red;
- position: absolute;
- top: 0px;
- pointer-events: none;
+ .collectionStackedTimeline-trim-handle {
+ background-color: $medium-blue;
+ height: 100%;
+ width: 5px;
+ cursor: ew-resize;
}
+ }
- .collectionStackedTimeline-marker-timeline {
- position: absolute;
- top: 2.5%;
- height: 95%;
- border-radius: 4px;
- &:hover {
- opacity: 1;
- }
+ .collectionStackedTimeline-selector {
+ position: absolute;
+ width: 10px;
+ top: 2.5%;
+ height: 95%;
+ background: $light-blue;
+ border-radius: 3px;
+ opacity: 0.3;
+ z-index: 500;
+ border-style: solid;
+ border-color: $medium-blue;
+ border-width: 1px;
+ }
- .collectionStackedTimeline-left-resizer,
- .collectionStackedTimeline-resizer {
- background: dimgrey;
- position: absolute;
- top: 0;
- height: 100%;
- width: 10px;
- pointer-events: all;
- cursor: ew-resize;
- z-index: 100;
- }
- .collectionStackedTimeline-resizer {
- right: 0;
- }
- .collectionStackedTimeline-left-resizer {
- left: 0;
- }
+ .collectionStackedTimeline-current {
+ width: 1px;
+ height: 100%;
+ background-color: $pink;
+ position: absolute;
+ top: 0px;
+ pointer-events: none;
+ }
+
+ .collectionStackedTimeline-marker-timeline {
+ position: absolute;
+ top: 2.5%;
+ height: 95%;
+ border-radius: 4px;
+ &:hover {
+ opacity: 1;
}
- .collectionStackedTimeline-waveform {
- position: absolute;
- width: 100%;
- height: 100%;
- top: 0;
- left: 0;
- pointer-events: none;
+ .collectionStackedTimeline-left-resizer,
+ .collectionStackedTimeline-resizer {
+ background: $medium-gray;
+ position: absolute;
+ top: 0;
+ height: 100%;
+ width: 10px;
+ pointer-events: all;
+ cursor: ew-resize;
+ z-index: 100;
+ }
+ .collectionStackedTimeline-resizer {
+ right: 0;
}
-} \ No newline at end of file
+ .collectionStackedTimeline-left-resizer {
+ left: 0;
+ }
+ }
+
+ .collectionStackedTimeline-waveform {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+ }
+}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index a2c95df6e..89da6692a 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,5 +1,12 @@
import React = require("react");
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import {
+ action,
+ computed,
+ IReactionDisposer,
+ observable,
+ reaction,
+ runInAction,
+} from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { Doc, DocListCast } from "../../../fields/Doc";
@@ -8,7 +15,16 @@ import { List } from "../../../fields/List";
import { listSpec, makeInterface } from "../../../fields/Schema";
import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
-import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent, returnTrue } from "../../../Utils";
+import {
+ emptyFunction,
+ formatTime,
+ OmitKeys,
+ returnFalse,
+ returnOne,
+ setupMoveUpEvents,
+ StopEvent,
+ returnTrue,
+} from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { LinkManager } from "../../util/LinkManager";
import { Scripting } from "../../util/Scripting";
@@ -18,9 +34,18 @@ import { undoBatch } from "../../util/UndoManager";
import { AudioWaveform } from "../AudioWaveform";
import { CollectionSubView } from "../collections/CollectionSubView";
import { LightboxView } from "../LightboxView";
-import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
+import {
+ DocAfterFocusFunc,
+ DocFocusFunc,
+ DocumentView,
+ DocumentViewProps,
+} from "../nodes/DocumentView";
import { LabelBox } from "../nodes/LabelBox";
import "./CollectionStackedTimeline.scss";
+import { Colors } from "../global/globalEnums";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SnappingManager } from "../../util/SnappingManager";
+import { DragManager } from "../../util/DragManager";
type PanZoomDocument = makeInterface<[]>;
const PanZoomDocument = makeInterface();
@@ -36,11 +61,21 @@ export type CollectionStackedTimelineProps = {
endTag: string;
mediaPath: string;
dictationKey: string;
+ trimming: boolean;
+ trimStart: number;
+ trimEnd: number;
+ trimDuration: number;
+ setStartTrim: (newStart: number) => void;
+ setEndTrim: (newEnd: number) => void;
};
@observer
-export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument, CollectionStackedTimelineProps>(PanZoomDocument) {
- @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
+export class CollectionStackedTimeline extends CollectionSubView<
+ PanZoomDocument,
+ CollectionStackedTimelineProps
+>(PanZoomDocument) {
+ @observable static SelectingRegion: CollectionStackedTimeline | undefined =
+ undefined;
static RangeScript: ScriptField;
static LabelScript: ScriptField;
static RangePlayScript: ScriptField;
@@ -50,48 +85,111 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
private _markerStart: number = 0;
@observable _markerEnd: number = 0;
- get duration() { return this.props.duration; }
- @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
+ get minLength() {
+ const rect = this._timeline?.getBoundingClientRect();
+ if (rect) {
+ return 0.05 * this.duration;
+ }
+ return 0;
+ }
+
+ get trimStart() {
+ return this.props.trimStart;
+ }
+
+ get trimEnd() {
+ return this.props.trimEnd;
+ }
+
+ get duration() {
+ return this.props.duration;
+ }
+
+ @computed get currentTime() {
+ return NumCast(this.layoutDoc._currentTimecode);
+ }
@computed get selectionContainer() {
- return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="collectionStackedTimeline-selector" style={{
- left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`,
- width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%`
- }} />;
+ return CollectionStackedTimeline.SelectingRegion !== this ? null : (
+ <div
+ className="collectionStackedTimeline-selector"
+ style={{
+ left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.props.trimDuration) * 100}%`,
+ width: `${(Math.abs(this._markerStart - this._markerEnd) / this.props.trimDuration) * 100}%`,
+ }}
+ />
+ );
}
constructor(props: any) {
super(props);
// onClick play scripts
- CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
- CollectionStackedTimeline.RangePlayScript = CollectionStackedTimeline.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
+ CollectionStackedTimeline.RangeScript =
+ CollectionStackedTimeline.RangeScript ||
+ ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
+ self: Doc.name,
+ scriptContext: "any",
+ clientX: "number",
+ })!;
+ CollectionStackedTimeline.RangePlayScript =
+ CollectionStackedTimeline.RangePlayScript ||
+ ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
+ self: Doc.name,
+ scriptContext: "any",
+ clientX: "number",
+ })!;
}
- componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); }
+ componentDidMount() {
+ document.addEventListener("keydown", this.keyEvents, true);
+ }
componentWillUnmount() {
document.removeEventListener("keydown", this.keyEvents, true);
- if (CollectionStackedTimeline.SelectingRegion === this) runInAction(() => CollectionStackedTimeline.SelectingRegion = undefined);
+ if (CollectionStackedTimeline.SelectingRegion === this) {
+ runInAction(
+ () => (CollectionStackedTimeline.SelectingRegion = undefined)
+ );
+ }
}
- anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
+ anchorStart = (anchor: Doc) =>
+ NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]))
anchorEnd = (anchor: Doc, val: any = null) => {
const endVal = NumCast(anchor[this.props.endTag], val);
- return NumCast(anchor._timecodeToHide, endVal === undefined ? null : endVal);
+ return NumCast(
+ anchor._timecodeToHide,
+ endVal === undefined ? null : endVal
+ );
}
- toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration));
+ toTimeline = (screen_delta: number, width: number) => {
+ return Math.max(
+ this.trimStart,
+ Math.min(this.trimEnd, (screen_delta / width) * this.props.trimDuration + this.trimStart));
+ }
+
rangeClickScript = () => CollectionStackedTimeline.RangeScript;
rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
// for creating key anchors with key events
@action
keyEvents = (e: KeyboardEvent) => {
- if (!(e.target instanceof HTMLInputElement) && this.props.isSelected(true)) {
+ if (
+ !(e.target instanceof HTMLInputElement) &&
+ this.props.isSelected(true)
+ ) {
switch (e.key) {
case " ":
if (!CollectionStackedTimeline.SelectingRegion) {
this._markerStart = this._markerEnd = this.currentTime;
CollectionStackedTimeline.SelectingRegion = this;
} else {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
+ CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.props.fieldKey,
+ this.props.startTag,
+ this.props.endTag,
+ this.currentTime
+ );
CollectionStackedTimeline.SelectingRegion = undefined;
}
}
@@ -101,7 +199,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
+ const linkTime = NumCast(
+ la2[this.props.startTag],
+ NumCast(la1[this.props.startTag])
+ );
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
@@ -118,10 +219,18 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
const wasPlaying = this.props.playing();
if (wasPlaying) this.props.Pause();
const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
- setupMoveUpEvents(this, e,
- action(e => {
- if (!wasSelecting && CollectionStackedTimeline.SelectingRegion !== this) {
- this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e) => {
+ if (
+ !wasSelecting &&
+ CollectionStackedTimeline.SelectingRegion !== this
+ ) {
+ this._markerStart = this._markerEnd = this.toTimeline(
+ clientX - rect.x,
+ rect.width
+ );
CollectionStackedTimeline.SelectingRegion = this;
}
this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
@@ -134,32 +243,148 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
this._markerStart = this._markerEnd;
this._markerEnd = tmp;
}
- if (!isClick && CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15)) {
- CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag,
- this._markerStart, this._markerEnd);
+ if (
+ !isClick &&
+ CollectionStackedTimeline.SelectingRegion === this &&
+ Math.abs(movement[0]) > 15 &&
+ !this.props.trimming
+ ) {
+ const anchor = CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.props.fieldKey,
+ this.props.startTag,
+ this.props.endTag,
+ this._markerStart,
+ this._markerEnd
+ );
+ setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
- (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined);
+ (!isClick || !wasSelecting) &&
+ (CollectionStackedTimeline.SelectingRegion = undefined);
}),
(e, doubleTap) => {
this.props.select(false);
- e.shiftKey && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
+ e.shiftKey &&
+ CollectionStackedTimeline.createAnchor(
+ this.rootDoc,
+ this.dataDoc,
+ this.props.fieldKey,
+ this.props.startTag,
+ this.props.endTag,
+ this.currentTime
+ );
!wasPlaying && doubleTap && this.props.Play();
},
- this.props.isSelected(true) || this.props.isContentActive(), undefined,
- () => !wasPlaying && this.props.setTime((clientX - rect.x) / rect.width * this.duration));
+ this.props.isSelected(true) || this.props.isContentActive(),
+ undefined,
+ () => {
+ !wasPlaying &&
+ (this.props.trimming && this.duration ?
+ this.props.setTime(((clientX - rect.x) / rect.width) * this.duration)
+ :
+ this.props.setTime(((clientX - rect.x) / rect.width) * this.props.trimDuration + this.trimStart)
+ );
+ }
+ );
}
+
+ }
+
+ @action
+ trimLeft = (e: React.PointerEvent): void => {
+ const rect = this._timeline?.getBoundingClientRect();
+ const clientX = e.movementX;
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e, [], []) => {
+ if (rect && this.props.isContentActive()) {
+ this.props.setStartTrim(Math.min(
+ Math.max(
+ this.trimStart + (e.movementX / rect.width) * this.duration,
+ 0
+ ),
+ this.trimEnd - this.minLength
+ ));
+ }
+ return false;
+ }),
+ emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ this.props.setStartTrim(0);
+ }
+ })
+ );
+ }
+
+ @action
+ trimRight = (e: React.PointerEvent): void => {
+ const rect = this._timeline?.getBoundingClientRect();
+ const clientX = e.movementX;
+ setupMoveUpEvents(
+ this,
+ e,
+ action((e, [], []) => {
+ if (rect && this.props.isContentActive()) {
+ this.props.setEndTrim(Math.max(
+ Math.min(
+ this.trimEnd + (e.movementX / rect.width) * this.duration,
+ this.duration
+ ),
+ this.trimStart + this.minLength
+ ));
+ }
+ return false;
+ }),
+ emptyFunction,
+ action((e, doubleTap) => {
+ if (doubleTap) {
+ this.props.setEndTrim(this.duration);
+ }
+ })
+ );
+ }
+
+ @action
+ internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
+ if (!de.embedKey && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!super.onInternalDrop(e, de)) return false;
+
+
+ // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
+
+ return true;
+ }
+
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0);
+ return false;
}
@undoBatch
@action
- static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number) {
+ static createAnchor(
+ rootDoc: Doc,
+ dataDoc: Doc,
+ fieldKey: string,
+ startTag: string,
+ endTag: string,
+ anchorStartTime?: number,
+ anchorEndTime?: number,
+ docAnchor?: Doc
+ ) {
if (anchorStartTime === undefined) return rootDoc;
- const anchor = Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(`"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`) as any,
+ const anchor = docAnchor ?? Docs.Create.LabelDocument({
+ title: ComputedField.MakeFunction(
+ `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
+ ) as any,
+ _stayInCollection: true,
useLinkSmallAnchor: true,
hideLinkButton: true,
annotationOn: rootDoc,
- _timelineLabel: true
+ _timelineLabel: true,
});
Doc.GetProto(anchor)[startTag] = anchorStartTime;
Doc.GetProto(anchor)[endTag] = anchorEndTime;
@@ -179,7 +404,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
if (this.props.playing()) this.props.Pause();
else this.props.playFrom(seekTimeInSeconds, endTime);
} else {
- if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
+ if (
+ seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) &&
+ endTime > NumCast(this.layoutDoc._currentTimecode)
+ ) {
if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
this.props.Pause();
} else {
@@ -194,39 +422,60 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
@action
clickAnchor = (anchorDoc: Doc, clientX: number) => {
- if (anchorDoc.isLinkButton) LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+ if (anchorDoc.isLinkButton) {
+ LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+ }
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
const endTime = this.anchorEnd(anchorDoc);
- if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
+ if (
+ seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 &&
+ endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4
+ ) {
if (this.props.playing()) this.props.Pause();
else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
const rect = this._timeline?.getBoundingClientRect();
- rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ rect &&
+ this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
} else {
- if (this.layoutDoc.autoPlayAnchors) this.props.playFrom(seekTimeInSeconds, endTime);
- else this.props.setTime(seekTimeInSeconds);
+ if (this.layoutDoc.autoPlayAnchors) {
+ this.props.playFrom(seekTimeInSeconds, endTime);
+ }
+ else {
+ this.props.setTime(seekTimeInSeconds);
+ }
}
return { select: true };
}
-
// makes sure no anchors overlaps each other by setting the correct position and width
- getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => {
+ getLevel = (
+ m: Doc,
+ placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]
+ ) => {
const timelineContentWidth = this.props.PanelWidth();
const x1 = this.anchorStart(m);
- const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration);
+ const x2 = this.anchorEnd(
+ m,
+ x1 + (10 / timelineContentWidth) * this.duration
+ );
let max = 0;
- const overlappedLevels = new Set(placed.map(p => {
- const y1 = p.anchorStartTime;
- const y2 = p.anchorEndTime;
- if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) ||
- (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) {
- max = Math.max(max, p.level);
- return p.level;
- }
- }));
+ const overlappedLevels = new Set(
+ placed.map((p) => {
+ const y1 = p.anchorStartTime;
+ const y2 = p.anchorEndTime;
+ if (
+ (x1 >= y1 && x1 <= y2) ||
+ (x2 >= y1 && x2 <= y2) ||
+ (y1 >= x1 && y1 <= x2) ||
+ (y2 >= x1 && y2 <= x2)
+ ) {
+ max = Math.max(max, p.level);
+ return p.level;
+ }
+ })
+ );
let level = max + 1;
for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j);
@@ -235,82 +484,185 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
}
dictationHeightPercent = 50;
- dictationHeight = () => this.props.PanelHeight() * (100 - this.dictationHeightPercent) / 100;
- timelineContentHeight = () => this.props.PanelHeight() * this.dictationHeightPercent / 100;
- dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight());
+ dictationHeight = () =>
+ (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100
+ timelineContentHeight = () =>
+ (this.props.PanelHeight() * this.dictationHeightPercent) / 100
+ dictationScreenToLocalTransform = () =>
+ this.props
+ .ScreenToLocalTransform()
+ .translate(0, -this.timelineContentHeight())
@computed get renderDictation() {
const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null);
- return !dictation ? (null) : <div style={{ position: "absolute", height: "100%", top: this.timelineContentHeight(), background: "tan" }}>
- <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- Document={dictation}
- PanelHeight={this.dictationHeight}
- isAnnotationOverlay={true}
- isDocumentActive={returnFalse}
- select={emptyFunction}
- scaling={returnOne}
- xMargin={25}
- yMargin={10}
- ScreenToLocalTransform={this.dictationScreenToLocalTransform}
- whenChildContentsActiveChanged={emptyFunction}
- removeDocument={returnFalse}
- moveDocument={returnFalse}
- addDocument={returnFalse}
- CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}>
- </DocumentView>
- </div>;
+ return !dictation ? null : (
+ <div
+ style={{
+ position: "absolute",
+ height: "100%",
+ top: this.timelineContentHeight(),
+ background: Colors.LIGHT_BLUE,
+ }}
+ >
+ <DocumentView
+ {...OmitKeys(this.props, [
+ "NativeWidth",
+ "NativeHeight",
+ "setContentView",
+ ]).omit}
+ Document={dictation}
+ PanelHeight={this.dictationHeight}
+ isAnnotationOverlay={true}
+ isDocumentActive={returnFalse}
+ select={emptyFunction}
+ scaling={returnOne}
+ xMargin={25}
+ yMargin={10}
+ ScreenToLocalTransform={this.dictationScreenToLocalTransform}
+ whenChildContentsActiveChanged={emptyFunction}
+ removeDocument={returnFalse}
+ moveDocument={returnFalse}
+ addDocument={returnFalse}
+ CollectionView={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ ></DocumentView>
+ </div>
+ );
}
@computed get renderAudioWaveform() {
- return !this.props.mediaPath ? (null) :
- <div className="collectionStackedTimeline-waveform" >
+ return !this.props.mediaPath ? null : (
+ <div className="collectionStackedTimeline-waveform">
<AudioWaveform
duration={this.duration}
mediaPath={this.props.mediaPath}
- dataDoc={this.dataDoc}
- PanelHeight={this.timelineContentHeight} />
- </div>;
+ layoutDoc={this.layoutDoc}
+ PanelHeight={this.timelineContentHeight}
+ trimming={this.props.trimming}
+ />
+ </div>
+ );
}
+
currentTimecode = () => this.currentTime;
render() {
const timelineContentWidth = this.props.PanelWidth();
- const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
- const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
+ const overlaps: {
+ anchorStartTime: number;
+ anchorEndTime: number;
+ level: number;
+ }[] = [];
+ const drawAnchors = this.childDocs.map((anchor) => ({
+ level: this.getLevel(anchor, overlaps),
+ anchor,
+ }));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
- const isActive = this.props.isContentActive() || this.props.isSelected(false);
- return <div className="collectionStackedTimeline" ref={(timeline: HTMLDivElement | null) => this._timeline = timeline}
- onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}>
- {drawAnchors.map(d => {
- const start = this.anchorStart(d.anchor);
- const end = this.anchorEnd(d.anchor, start + 10 / timelineContentWidth * this.duration);
- const left = start / this.duration * timelineContentWidth;
- const top = d.level / maxLevel * this.timelineContentHeight();
- const timespan = end - start;
- return this.props.Document.hideAnchors ? (null) :
- <div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]}
- style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${this.timelineContentHeight() / maxLevel}px` }}
- onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} >
- <StackedTimelineAnchor {...this.props}
- mark={d.anchor}
- rangeClickScript={this.rangeClickScript}
- rangePlayScript={this.rangePlayScript}
- left={left}
- top={top}
- width={timelineContentWidth * timespan / this.duration}
- height={this.timelineContentHeight() / maxLevel}
- toTimeline={this.toTimeline}
- layoutDoc={this.layoutDoc}
- currentTimecode={this.currentTimecode}
- _timeline={this._timeline}
- stackedTimeline={this}
- />
- </div>;
- })}
- {this.selectionContainer}
- {this.renderAudioWaveform}
- {this.renderDictation}
-
- <div className="collectionStackedTimeline-current" style={{ left: `${this.currentTime / this.duration * 100}%` }} />
- </div>;
+ const isActive =
+ this.props.isContentActive() || this.props.isSelected(false);
+ return (<div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined }}>
+ <div
+ className="collectionStackedTimeline"
+ ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+ onClick={(e) => isActive && StopEvent(e)}
+ onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)}
+ >
+ {drawAnchors.map((d) => {
+
+ const start = this.anchorStart(d.anchor);
+ const end = this.anchorEnd(
+ d.anchor,
+ start + (10 / timelineContentWidth) * this.duration
+ );
+ const left = this.props.trimming ?
+ (start / this.duration) * timelineContentWidth
+ : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
+ const top = (d.level / maxLevel) * this.timelineContentHeight();
+ const timespan = end - start;
+ const width = (timespan / this.props.trimDuration) * timelineContentWidth;
+ const height = this.timelineContentHeight() / maxLevel;
+ return this.props.Document.hideAnchors ? null : (
+ <div
+ className={"collectionStackedTimeline-marker-timeline"}
+ key={d.anchor[Id]}
+ style={{
+ left,
+ top,
+ width: `${width}px`,
+ height: `${height}px`,
+ }}
+ onClick={(e) => {
+ this.props.playFrom(start, this.anchorEnd(d.anchor));
+ e.stopPropagation();
+ }}
+ >
+ <StackedTimelineAnchor
+ {...this.props}
+ mark={d.anchor}
+ rangeClickScript={this.rangeClickScript}
+ rangePlayScript={this.rangePlayScript}
+ left={left}
+ top={top}
+ width={width}
+ height={height}
+ toTimeline={this.toTimeline}
+ layoutDoc={this.layoutDoc}
+ currentTimecode={this.currentTimecode}
+ _timeline={this._timeline}
+ stackedTimeline={this}
+ trimStart={this.trimStart}
+ trimEnd={this.trimEnd}
+ />
+ </div>
+ );
+ })}
+ {!this.props.trimming && this.selectionContainer}
+ {this.renderAudioWaveform}
+ {this.renderDictation}
+
+ <div
+ className="collectionStackedTimeline-current"
+ style={{
+ left: this.props.trimming
+ ? `${(this.currentTime / this.duration) * 100}%`
+ : `${(this.currentTime - this.trimStart) / (this.trimEnd - this.trimStart) * 100}%`,
+ }}
+ />
+
+ {this.props.trimming && (
+ <>
+ <div
+ className="collectionStackedTimeline-trim-shade"
+ style={{ width: `${(this.trimStart / this.duration) * 100}%` }}
+ ></div>
+
+ <div
+ className="collectionStackedTimeline-trim-controls"
+ style={{
+ left: `${(this.trimStart / this.duration) * 100}%`,
+ width: `${((this.trimEnd - this.trimStart) / this.duration) * 100
+ }%`,
+ }}
+ >
+ <div
+ className="collectionStackedTimeline-trim-handle"
+ onPointerDown={this.trimLeft}
+ ></div>
+ <div
+ className="collectionStackedTimeline-trim-handle"
+ onPointerDown={this.trimRight}
+ ></div>
+ </div>
+
+ <div
+ className="collectionStackedTimeline-trim-shade"
+ style={{
+ left: `${(this.trimEnd / this.duration) * 100}%`,
+ width: `${((this.duration - this.trimEnd) / this.duration) * 100
+ }%`,
+ }}
+ ></div>
+ </>
+ )}
+ </div>
+ </div>);
}
}
@@ -335,6 +687,8 @@ interface StackedTimelineAnchorProps {
currentTimecode: () => number;
isSelected: (outsideReaction?: boolean) => boolean;
stackedTimeline: CollectionStackedTimeline;
+ trimStart: number;
+ trimEnd: number;
}
@observer
class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
@@ -345,22 +699,41 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
this._lastTimecode = this.props.currentTimecode();
}
componentDidMount() {
- this._disposer = reaction(() => this.props.currentTimecode(),
+ this._disposer = reaction(
+ () => this.props.currentTimecode(),
(time) => {
- const dictationDoc = Cast(this.props.layoutDoc["data-dictation"], Doc, null);
- const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc);
- if (!LightboxView.LightboxDoc
+ const dictationDoc = Cast(
+ this.props.layoutDoc["data-dictation"],
+ Doc,
+ null
+ );
+ const isDictation =
+ dictationDoc &&
+ DocListCast(this.props.mark.links).some(
+ (link) =>
+ Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc
+ );
+ if (
+ !LightboxView.LightboxDoc &&
// bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront.
// for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
/*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
- && DocListCast(this.props.mark.links).length &&
+ DocListCast(this.props.mark.links).length &&
time > NumCast(this.props.mark[this.props.startTag]) &&
time < NumCast(this.props.mark[this.props.endTag]) &&
- this._lastTimecode < NumCast(this.props.mark[this.props.startTag])) {
- LinkManager.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true);
+ this._lastTimecode < NumCast(this.props.mark[this.props.startTag])
+ ) {
+ LinkManager.FollowLink(
+ undefined,
+ this.props.mark,
+ this.props as any as DocumentViewProps,
+ false,
+ true
+ );
}
this._lastTimecode = time;
- });
+ }
+ );
}
componentWillUnmount() {
this._disposer?.();
@@ -373,57 +746,136 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
return this.props.toTimeline(e.clientX - rect.x, rect.width);
};
const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
- const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
- if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
- else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
+ const timelineOnly =
+ Cast(anchor[this.props.startTag], "number", null) !== undefined;
+ if (timelineOnly) {
+ Doc.SetInPlace(
+ anchor,
+ left ? this.props.startTag : this.props.endTag,
+ time,
+ true
+ );
+ }
+ else {
+ left
+ ? (anchor._timecodeToShow = time)
+ : (anchor._timecodeToHide = time);
+ }
return false;
};
- setupMoveUpEvents(this, e,
+ setupMoveUpEvents(
+ this,
+ e,
(e) => changeAnchor(anchor, left, newTime(e)),
(e) => {
this.props.setTime(newTime(e));
this.props._timeline?.releasePointerCapture(e.pointerId);
},
- emptyFunction);
+ emptyFunction
+ );
}
- renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
+
+ @action
+ computeTitle = () => {
+ const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
+ const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+ return `#${formatTime(start)}-${formatTime(end)}`;
+ }
+
+ renderInner = computedFn(function (
+ this: StackedTimelineAnchor,
+ mark: Doc,
+ script: undefined | (() => ScriptField),
+ doublescript: undefined | (() => ScriptField),
+ x: number,
+ y: number,
+ width: number,
+ height: number
+ ) {
const anchor = observable({ view: undefined as any });
- const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
+ const focusFunc = (
+ doc: Doc,
+ willZoom?: boolean,
+ scale?: number,
+ afterFocus?: DocAfterFocusFunc,
+ docTransform?: Transform
+ ) => {
this.props.playLink(mark);
this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
};
return {
- anchor, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- ref={action((r: DocumentView | null) => anchor.view = r)}
- Document={mark}
- DataDoc={undefined}
- renderDepth={this.props.renderDepth + 1}
- LayoutTemplate={undefined}
- LayoutTemplateString={LabelBox.LayoutString("data")}
- isDocumentActive={returnFalse}
- PanelWidth={() => width}
- PanelHeight={() => height}
- ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)}
- focus={focusFunc}
- rootSelected={returnFalse}
- onClick={script}
- onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
- ignoreAutoHeight={false}
- hideResizeHandles={true}
- bringToFront={emptyFunction}
- scriptContext={this.props.stackedTimeline} />
+ anchor,
+ view: (
+ <DocumentView
+ key="view"
+ {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ ref={action((r: DocumentView | null) => (anchor.view = r))}
+ Document={mark}
+ DataDoc={undefined}
+ renderDepth={this.props.renderDepth + 1}
+ LayoutTemplate={undefined}
+ LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())}
+ isDocumentActive={returnFalse}
+ PanelWidth={() => width}
+ PanelHeight={() => height}
+ ScreenToLocalTransform={() =>
+ this.props.ScreenToLocalTransform().translate(-x, -y)
+ }
+ focus={focusFunc}
+ rootSelected={returnFalse}
+ onClick={script}
+ onDoubleClick={
+ this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript
+ }
+ ignoreAutoHeight={false}
+ hideResizeHandles={true}
+ bringToFront={emptyFunction}
+ scriptContext={this.props.stackedTimeline}
+ />
+ ),
};
});
+
render() {
- const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.props.left, this.props.top, this.props.width, this.props.height);
- return <>
- {inner.view}
- {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) :
- <>
- <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
- <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
- </>}
- </>;
+ const inner = this.renderInner(
+ this.props.mark,
+ this.props.rangeClickScript,
+ this.props.rangePlayScript,
+ this.props.left,
+ this.props.top,
+ this.props.width,
+ this.props.height
+ );
+ return (
+ <>
+ {inner.view}
+ {!inner.anchor.view ||
+ !SelectionManager.IsSelected(inner.anchor.view) ? null : (
+ <>
+ <div
+ key="left"
+ className="collectionStackedTimeline-left-resizer"
+ onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
+ />
+ <div
+ key="right"
+ className="collectionStackedTimeline-resizer"
+ onPointerDown={(e) =>
+ this.onAnchorDown(e, this.props.mark, false)
+ }
+ />
+ </>
+ )}
+ </>
+ );
}
}
-Scripting.addGlobal(function formatToTime(time: number): any { return formatTime(time); }); \ No newline at end of file
+Scripting.addGlobal(function formatToTime(time: number): any {
+ return formatTime(time);
+});
+Scripting.addGlobal(function min(num1: number, num2: number): number {
+ return Math.min(num1, num2);
+});
+Scripting.addGlobal(function max(num1: number, num2: number): number {
+ return Math.max(num1, num2);
+}); \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 4b123c8b6..2f002736d 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -14,6 +14,30 @@
width: 100%;
}
+// TODO:glr Turn this into a seperate class
+.documentButtonMenu {
+ position: relative;
+ height: fit-content;
+ border-bottom: $standard-border;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ align-content: center;
+ padding: 5px 0 5px 0;
+
+ .documentExplanation {
+ width: 90%;
+ margin: 5px;
+ font-size: 11px;
+ background-color: $light-blue;
+ color: $medium-blue;
+ padding: 10px;
+ border-radius: 10px;
+ border: solid 2px $medium-blue;
+ }
+}
+
.collectionStackingView,
.collectionMasonryView {
height: 100%;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 4fedda1bd..648ff5087 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CursorProperty } from "csstype";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
+import { DataSym, Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../fields/Doc";
import { collectionSchema, documentSchema } from "../../../fields/documentSchemas";
import { Id } from "../../../fields/FieldSymbols";
import { List } from "../../../fields/List";
@@ -11,7 +11,7 @@ import { listSpec, makeInterface } from "../../../fields/Schema";
import { SchemaHeaderField } from "../../../fields/SchemaHeaderField";
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from "../../../fields/util";
-import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, Utils, returnTrue, returnEmptyDoclist, returnEmptyFilter } from "../../../Utils";
import { DocUtils, Docs } from "../../documents/Documents";
import { DragManager, dropActionType } from "../../util/DragManager";
import { SnappingManager } from "../../util/SnappingManager";
@@ -23,12 +23,14 @@ import { EditableView } from "../EditableView";
import { LightboxView } from "../LightboxView";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView";
-import { StyleProp } from "../StyleProvider";
+import { StyleProp, DefaultStyleProvider } from "../StyleProvider";
import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow";
import "./CollectionStackingView.scss";
import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionViewType } from "./CollectionView";
+import { FontIconBox } from "../nodes/button/FontIconBox";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
const _global = (window /* browser */ || global /* node */) as any;
type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>;
@@ -234,6 +236,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
rootSelected={this.rootSelected}
+ showTitle={this.props.childShowTitle}
dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType}
onClick={this.onChildClickHandler}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -514,6 +517,47 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0));
}
+ @computed get buttonMenu() {
+ const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+ // TODO:glr Allow support for multiple buttons
+ if (menuDoc) {
+ const width: number = NumCast(menuDoc._width, 30);
+ const height: number = NumCast(menuDoc._height, 30);
+ console.log(menuDoc.title, width, height);
+ return (<div className="buttonMenu-docBtn"
+ style={{ width: width, height: height }}>
+ <DocumentView
+ Document={menuDoc}
+ DataDoc={menuDoc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={() => 35}
+ PanelHeight={() => 35}
+ renderDepth={this.props.renderDepth}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>
+ );
+ }
+ }
+
@computed get nativeWidth() { return this.props.NativeWidth?.() ?? Doc.NativeWidth(this.layoutDoc); }
@computed get nativeHeight() { return this.props.NativeHeight?.() ?? Doc.NativeHeight(this.layoutDoc); }
@@ -529,34 +573,49 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
SetValue: this.addGroup,
contents: "+ ADD A GROUP"
};
+ const buttonMenu = this.rootDoc.buttonMenu;
+ const noviceExplainer = this.rootDoc.explainer;
return (
- <div className="collectionStackingMasonry-cont" >
- <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
- ref={this.createRef}
- style={{
- overflowY: this.props.isContentActive() ? "auto" : "hidden",
- background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
- pointerEvents: this.backgroundEvents ? "all" : undefined
- }}
- onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
- onDrop={this.onExternalDrop.bind(this)}
- onContextMenu={this.onContextMenu}
- onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
- {this.renderedSections}
- {!this.showAddAGroup ? (null) :
- <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
- style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
- <EditableView {...editableViewProps} />
- </div>}
- {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
- <Switch
- onChange={this.onToggle}
- onClick={this.onToggle}
- defaultChecked={true}
- checkedChildren="edit"
- unCheckedChildren="view"
- />} */}
- </div> </div>
+ <>
+ {buttonMenu || noviceExplainer ? <div className="documentButtonMenu">
+ {buttonMenu ? this.buttonMenu : null}
+ {Doc.UserDoc().noviceMode && noviceExplainer ?
+ <div className="documentExplanation">
+ {noviceExplainer}
+ </div>
+ : null
+ }
+ </div> : null}
+ <div className="collectionStackingMasonry-cont" >
+ <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
+ ref={this.createRef}
+ style={{
+ overflowY: this.props.isContentActive() ? "auto" : "hidden",
+ background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
+ pointerEvents: this.backgroundEvents ? "all" : undefined
+ }}
+ onScroll={action(e => this._scroll = e.currentTarget.scrollTop)}
+ onDrop={this.onExternalDrop.bind(this)}
+ onContextMenu={this.onContextMenu}
+ onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} >
+ {this.renderedSections}
+ {!this.showAddAGroup ? (null) :
+ <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
+ style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
+ <EditableView {...editableViewProps} />
+ </div>}
+ {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
+ <Switch
+ onChange={this.onToggle}
+ onClick={this.onToggle}
+ defaultChecked={true}
+ checkedChildren="edit"
+ unCheckedChildren="view"
+ />} */}
+ </div>
+ </div>
+ </>
+
);
}
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 47733994b..58289a161 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -111,7 +111,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4");
@action pointerLeave = () => this._background = "inherit";
- textCallback = (char: string) => this.addNewTextDoc("", false, true);
+ textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true);
@action
addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => {
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b70df93da..e662f3ddf 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -216,7 +216,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]);
const someMoved = !docDragData.userDropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag));
if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop);
- if ((!dropAction || dropAction === "move" || someMoved) && docDragData.moveDocument) {
+ if ((!dropAction || dropAction === "same" || dropAction === "move" || someMoved) && docDragData.moveDocument) {
const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d);
const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d);
if (movedDocs.length) {
@@ -321,12 +321,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
});
} else {
- let srcUrl: string | undefined;
- let srcWeb: Doc | undefined;
- if (SelectionManager.Views().length) {
- srcWeb = SelectionManager.Views()[0].props.Document;
- srcUrl = (srcWeb.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
- }
+ const srcWeb = SelectionManager.Docs().lastElement();
+ const srcUrl = (srcWeb?.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0];
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
@@ -360,7 +356,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const batch = UndoManager.StartBatch("youtube upload");
const generatedDocuments: Doc[] = [];
- this.slowLoadDocuments((uriList || text).split("v=")[1], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
+ this.slowLoadDocuments((uriList || text).split("v=")[1].split("&")[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end);
return;
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 89d42439e..3852987b9 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -8,7 +8,7 @@ import { Document, listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
-import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils';
+import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, emptyFunction } from '../../../Utils';
import { DocUtils } from '../../documents/Documents';
import { CurrentUserUtils } from '../../util/CurrentUserUtils';
import { DocumentManager } from '../../util/DocumentManager';
@@ -26,6 +26,7 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import { TreeView } from "./TreeView";
import React = require("react");
+import { Transform } from '../../util/Transform';
const _global = (window /* browser */ || global /* node */) as any;
export type collectionTreeViewProps = {
@@ -107,7 +108,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
if (dragData) {
- const isInTree = () => dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d));
+ const isInTree = () => Doc.AreProtosEqual(dragData.treeViewDoc, this.props.Document) || dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d));
dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction;
}
}
@@ -221,8 +222,9 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
}
childContextMenuItems = () => {
const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
- const filterScripts = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: filterScripts[i], label }));
+ const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
+ const icons = StrListCast(this.doc.childContextMenuIcons);
+ return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
}
@computed get treeViewElements() {
TraceMobx();
@@ -265,13 +267,50 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
return hideTitle ? (null) : (this.outlineMode ? this.documentTitle : this.editableTitle)(this.treeChildren);
}
- @computed get renderClearButton() {
- return !this.doc.treeViewShowClearButton ? (null) : <div key="toolbar">
- <button className="toolbar-button round-button" title="Empty" onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}>
- <FontAwesomeIcon icon={"trash"} size="sm" />
- </button>
- </div >;
+ return35 = () => 35;
+ @computed get buttonMenu() {
+ const menuDoc: Doc = Cast(this.rootDoc.buttonMenuDoc, Doc, null);
+ // To create a multibutton menu add a CollectionLinearView
+ if (menuDoc) {
+
+ const width: number = NumCast(menuDoc._width, 30);
+ const height: number = NumCast(menuDoc._height, 30);
+ console.log(menuDoc.title, width, height);
+ return (<div className="buttonMenu-docBtn"
+ style={{ width: width, height: height }}>
+ <DocumentView
+ Document={menuDoc}
+ DataDoc={menuDoc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={Transform.Identity}
+ PanelWidth={this.return35}
+ PanelHeight={this.return35}
+ renderDepth={this.props.renderDepth + 1}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ />
+ </div>);
+ }
}
+
+ @observable _explainerHeight: number = 0;
+
@computed get nativeWidth() { return Doc.NativeWidth(this.Document, undefined, true); }
@computed get nativeHeight() { return Doc.NativeHeight(this.Document, undefined, true); }
@@ -295,22 +334,34 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
TraceMobx();
const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined;
+ const buttonMenu = this.rootDoc.buttonMenu;
+ const noviceExplainer = this.rootDoc.explainer;
return !(this.doc instanceof Doc) || !this.treeChildren ? (null) :
- <div className="collectionTreeView-container"
- style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
- onContextMenu={this.onContextMenu}>
- <div className="collectionTreeView-dropTarget"
- style={{ background: background(), paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
- onWheel={e => e.stopPropagation()}
- onDrop={this.onTreeDrop}
- ref={this.createTreeDropTarget}>
- {this.titleBar}
- {this.renderClearButton}
- <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
- {this.treeViewElements}
- </ul>
- </div >
- </div>;
+ <>
+ {this.titleBar}
+ <div className="collectionTreeView-container"
+ style={this.outlineMode ? { transform: `scale(${this.contentScaling})`, width: `calc(${100 / this.contentScaling}%)` } : {}}
+ onContextMenu={this.onContextMenu}>
+ {buttonMenu || noviceExplainer ? <div className="documentButtonMenu" ref={action((r: HTMLDivElement) => r && (this._explainerHeight = r.getBoundingClientRect().height))}>
+ {buttonMenu ? this.buttonMenu : null}
+ {Doc.UserDoc().noviceMode && noviceExplainer ?
+ <div className="documentExplanation">
+ {noviceExplainer}
+ </div>
+ : null
+ }
+ </div> : null}
+ <div className="collectionTreeView-dropTarget"
+ style={{ background: background(), height: `calc(100% - ${this._explainerHeight}px)`, paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingBottom: `${this.paddingBot()}px`, paddingTop: `${this.paddingTop()}px`, pointerEvents: pointerEvents() }}
+ onWheel={e => e.stopPropagation()}
+ onDrop={this.onTreeDrop}
+ ref={this.createTreeDropTarget}>
+ <ul className={`no-indent${this.outlineMode ? "-outline" : ""}`} >
+ {this.treeViewElements}
+ </ul>
+ </div >
+ </div>
+ </>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 229e93b9e..38e027fb3 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -24,7 +24,7 @@ import { CollectionCarouselView } from './CollectionCarouselView';
import { CollectionDockingView } from "./CollectionDockingView";
import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
import { CollectionGridView } from './collectionGrid/CollectionGridView';
-import { CollectionLinearView } from './CollectionLinearView';
+import { CollectionLinearView } from './collectionLinear';
import CollectionMapView from './CollectionMapView';
import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
@@ -71,6 +71,7 @@ export interface CollectionViewProps extends FieldViewProps {
childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
childDocumentsActive?: () => boolean;// whether child documents can be dragged if collection can be dragged (eg., in a when a Pile document is in startburst mode)
childFitWidth?: () => boolean;
+ childShowTitle?: () => string;
childOpacity?: () => number;
childContextMenuItems?: () => { script: ScriptField, label: string }[];
childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
@@ -165,7 +166,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
!Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" });
subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" });
- if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.annotationOn) {
+ if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) {
const existingVm = ContextMenu.Instance.findByDescription(category);
const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : [];
catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" });
@@ -245,12 +246,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null);
@computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); }
- /**
- * Shows the filter icon if it's a user-created collection which isn't a dashboard and has some docFilters applied on it or on the current dashboard.
- */
- @computed get showFilterIcon() {
- return this.props.Document.viewType !== CollectionViewType.Docking && !Doc.IsSystem(this.props.Document) && this._subView?.IsFiltered();
- }
@observable _subView: any = undefined;
@@ -279,12 +274,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}>
{this.showIsTagged()}
{this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)}
- {this.showFilterIcon ?
- <FontAwesomeIcon icon={"filter"} size="lg"
- style={{ position: 'absolute', top: '1%', right: '1%', cursor: "pointer", padding: 1, color: this.showFilterIcon === "hasFilter" ? '#18c718bd' : "orange", zIndex: 1 }}
- onPointerDown={action(e => { this.props.select(false); CurrentUserUtils.propertiesWidth = 250; e.stopPropagation(); })}
- />
- : (null)}
</div>);
}
}
diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss
index a963f1cb9..7f62ecaa0 100644
--- a/src/client/views/collections/TabDocView.scss
+++ b/src/client/views/collections/TabDocView.scss
@@ -1,3 +1,6 @@
+@import "../global/globalCssVariables.scss";
+
+
input.lm_title:focus,
input.lm_title {
max-width: unset !important;
@@ -57,12 +60,11 @@ input.lm_title {
}
}
-.miniMap-hidden,
.miniMap {
position: absolute;
overflow: hidden;
- right: 10;
- bottom: 10;
+ right: 15;
+ bottom: 15;
border: solid 1px;
box-shadow: black 0.4vw 0.4vw 0.8vw;
width: 100%;
@@ -82,17 +84,21 @@ input.lm_title {
}
.miniMap-hidden {
+ cursor: pointer;
position: absolute;
- bottom: 0;
- right: 0;
- width: 45px;
- height: 45px;
- transform: translate(20px, 20px) rotate(45deg);
- border-radius: 30px;
+ bottom: 5;
+ display: flex;
+ right: 5;
+ width: 25px;
+ height: 25px;
+ border-radius: 3px;
padding: 2px;
+ justify-content: center;
+ align-items: center;
+ align-content: center;
+ background-color: $light-gray;
- >svg {
- margin-top: 3px;
- transform: translate(0px, 7px);
+ &:hover {
+ box-shadow: none;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 117dba4de..eb95bb913 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -22,6 +22,7 @@ import { SelectionManager } from '../../util/SelectionManager';
import { SnappingManager } from '../../util/SnappingManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { Colors, Shadows } from '../global/globalEnums';
import { LightboxView } from '../LightboxView';
import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import { PinProps, PresBox, PresMovement } from '../nodes/trails';
@@ -32,7 +33,6 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
import { CollectionView, CollectionViewType } from './CollectionView';
import "./TabDocView.scss";
import React = require("react");
-import Color = require('color');
const _global = (window /* browser */ || global /* node */) as any;
interface TabDocViewProps {
@@ -123,6 +123,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
tab.element[0].prepend(iconWrap);
tab._disposers.layerDisposer = reaction(() => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
({ layer, color }) => {
+ // console.log("TabDocView: " + this.tabColor);
+ // console.log("lightOrDark: " + lightOrDark(this.tabColor));
const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color
titleEle.style.color = textColor;
titleEle.style.backgroundColor = "transparent";
@@ -131,12 +133,6 @@ export class TabDocView extends React.Component<TabDocViewProps> {
moreInfoDrag.style.backgroundColor = textColor;
tab.element[0].style.background = !layer ? color : "dimgrey";
}, { fireImmediately: true });
- // TODO:glr fix
- // tab.element[0].style.borderTopRightRadius = "8px";
- // tab.element[0].children[1].appendChild(toggle);
- // tab._disposers.layerDisposer = reaction(() =>
- // ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }),
- // ({ layer, color }) => toggle.style.background = !layer ? color : "dimgrey", { fireImmediately: true });
}
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: MouseEvent) => {
@@ -390,8 +386,16 @@ export class TabDocView extends React.Component<TabDocViewProps> {
background={this.miniMapColor}
document={this._document}
tabView={this.tabView} />
- <Tooltip style={{ display: this.disableMinimap() ? "none" : undefined }} key="ttip" title={<div className="dash-tooltip">{"toggle minimap"}</div>}>
- <div className="miniMap-hidden" onPointerDown={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
+ <Tooltip key="ttip" title={<div className="dash-tooltip">{this._document.hideMinimap ? "Open minimap" : "Close minimap"}</div>}>
+ <div className="miniMap-hidden"
+ style={{
+ display: this.disableMinimap() || this._document._viewType !== "freeform" ? "none" : undefined,
+ color: this._document.hideMinimap ? Colors.BLACK : Colors.WHITE,
+ backgroundColor: this._document.hideMinimap ? Colors.LIGHT_GRAY : Colors.MEDIUM_BLUE,
+ boxShadow: this._document.hideMinimap ? Shadows.STANDARD_SHADOW : undefined
+ }}
+ onPointerDown={e => e.stopPropagation()}
+ onClick={action(e => { e.stopPropagation(); this._document!.hideMinimap = !this._document!.hideMinimap; })} >
<FontAwesomeIcon icon={"globe-asia"} size="lg" />
</div>
</Tooltip>
@@ -434,7 +438,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> {
case StyleProp.DocContents:
const background = doc.type === DocumentType.PDF ? "red" : doc.type === DocumentType.IMG ? "blue" : doc.type === DocumentType.RTF ? "orange" :
doc.type === DocumentType.VID ? "purple" : doc.type === DocumentType.WEB ? "yellow" : "gray";
- return doc.type === DocumentType.COL ?
+ return doc.type === DocumentType.COL || doc.type === DocumentType.INK ?
undefined :
<div style={{ width: doc[WidthSym](), height: doc[HeightSym](), position: "absolute", display: "block", background }} />;
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 86fd21677..a3da0e0e4 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -54,7 +54,7 @@ export interface TreeViewProps {
indentDocument?: (editTitle: boolean) => void;
outdentDocument?: (editTitle: boolean) => void;
ScreenToLocalTransform: () => Transform;
- contextMenuItems: { script: ScriptField, filter: ScriptField, label: string }[];
+ contextMenuItems: { script: ScriptField, filter: ScriptField, icon: string, label: string }[];
dontRegisterView?: boolean;
styleProvider?: StyleProviderFunc | undefined;
treeViewHideHeaderFields: () => boolean;
@@ -154,8 +154,8 @@ export class TreeView extends React.Component<TreeViewProps> {
} else {
// choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias
// this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right");
- // choose an appropriate alias or make one -- -- choose the first alias that (1) the user owns, (2) has no context field - if I own it and someone else does not have it open,, otherwise create an alias
- this.props.addDocTab(this.props.document, "add:right");
+ const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail);
+ this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right");
}
}
@@ -240,10 +240,13 @@ export class TreeView extends React.Component<TreeViewProps> {
}
makeFolder = () => {
- const folder = Docs.Create.TreeDocument([], { title: "-folder-", _stayInCollection: true, isFolder: true });
+ const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true });
TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView };
return this.props.addDocument(folder);
}
+ deleteFolder = () => {
+ return this.props.removeDoc?.(this.doc);
+ }
preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
const dragData = de.complete.docDragData;
@@ -478,13 +481,16 @@ export class TreeView extends React.Component<TreeViewProps> {
}
@computed get validExpandViewTypes() {
- if (this.doc.viewType === CollectionViewType.Docking) return [this.fieldKey];
+ if (this.props.treeView.dashboardMode && Doc.UserDoc().noviceMode) {
+ return [this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : "layout"];
+ }
const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : "";
const links = () => DocListCast(this.doc.links).length ? "links" : "";
- const data = () => this.childDocs && !this.props.treeView.dashboardMode ? this.fieldKey : "";
+ const data = () => this.childDocs ? this.fieldKey : "";
const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases";
const fields = () => Doc.UserDoc().noviceMode ? "" : "fields";
- return [data(), "layout", ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
+ const layout = this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"];
+ return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m);
}
@action
expandNextviewType = () => {
@@ -512,10 +518,12 @@ export class TreeView extends React.Component<TreeViewProps> {
DocumentViewInternal.SelectAfterContextMenu = true;
}
contextMenuItems = () => {
- const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" };
- const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" };
- const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" };
- return [...this.props.contextMenuItems.filter(mi => !mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result), ... (this.doc.isFolder ? [makeFolder] :
+ const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, icon: "folder-plus", label: "New Folder" };
+ const deleteFolder = { script: ScriptField.MakeFunction(`scriptContext.deleteFolder()`, { scriptContext: "any" })!, icon: "folder-plus", label: "Delete Folder" };
+ const folderOp = this.childDocs?.length ? makeFolder : deleteFolder;
+ const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: "copy", label: "Open Alias" };
+ const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: "eye", label: "Focus or Open" };
+ return [...this.props.contextMenuItems.filter(mi => !mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result), ... (this.doc.isFolder ? [folderOp] :
Doc.IsSystem(this.doc) ? [] :
this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ?
[openAlias, makeFolder] :
@@ -525,7 +533,8 @@ export class TreeView extends React.Component<TreeViewProps> {
childContextMenuItems = () => {
const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []);
const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []);
- return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], label }));
+ const icons = StrListCast(this.doc.childContextMenuIcons);
+ return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label }));
}
onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick));
@@ -835,7 +844,7 @@ export class TreeView extends React.Component<TreeViewProps> {
dontRegisterView: boolean | undefined,
observerHeight: (ref: any) => void,
unobserveHeight: (ref: any) => void,
- contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string }[])
+ contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[])
) {
const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
@@ -852,6 +861,7 @@ export class TreeView extends React.Component<TreeViewProps> {
}
const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => {
+ if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return;
const fieldKey = Doc.LayoutFieldKey(newParent);
if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) {
remove(child);
@@ -868,7 +878,7 @@ export class TreeView extends React.Component<TreeViewProps> {
const childLayout = Doc.Layout(pair.layout);
const rowHeight = () => {
const aspect = Doc.NativeAspect(childLayout);
- return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
+ return (aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]());
};
return <TreeView key={child[Id]} ref={r => treeViewRefs.set(child, r ? r : undefined)}
document={pair.layout}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 37444a9dc..9fed82dae 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -7,6 +7,7 @@ import { Cast, NumCast, StrCast } from "../../../../fields/Types";
import { aggregateBounds } from "../../../../Utils";
import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import React = require("react");
+import { ColorScheme } from "../../../util/SettingsManager";
export interface ViewDefBounds {
type: string;
@@ -361,7 +362,7 @@ export function computeTimelineLayout(
groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
}
- const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+ const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 16258404d..bb4cae8c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,12 +1,13 @@
import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
+import { Doc, Field } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
import { List } from "../../../../fields/List";
-import { NumCast, StrCast } from "../../../../fields/Types";
+import { NumCast } from "../../../../fields/Types";
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
-import { DocumentType } from "../../../documents/DocumentTypes";
+import { CurrentUserUtils } from "../../../util/CurrentUserUtils";
import { LinkManager } from "../../../util/LinkManager";
+import { ColorScheme } from "../../../util/SettingsManager";
import { SnappingManager } from "../../../util/SnappingManager";
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
@@ -41,10 +42,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const { A, B, LinkDocs } = this.props;
const linkDoc = LinkDocs[0];
if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
- setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
+ setTimeout(action(() => this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = A.rootDoc.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = B.rootDoc.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
const adiv = acont.length ? acont[0] : A.ContentDiv;
const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
const a = adiv.getBoundingClientRect();
@@ -69,8 +70,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
const m = targetAhyperlink.getBoundingClientRect();
const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- linkDoc.anchor1_x = Math.min(1, mp[0] / A.props.PanelWidth()) * 100;
- linkDoc.anchor1_y = Math.min(1, mp[1] / A.props.PanelHeight()) * 100;
+ const mpx = mp[0] / A.props.PanelWidth();
+ const mpy = mp[1] / A.props.PanelHeight();
+ if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
}
if (!targetBhyperlink) {
if (linkDoc.linkAutoMove) {
@@ -80,8 +83,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
} else {
const m = targetBhyperlink.getBoundingClientRect();
const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
- linkDoc.anchor2_x = Math.min(1, mp[0] / B.props.PanelWidth()) * 100;
- linkDoc.anchor2_y = Math.min(1, mp[1] / B.props.PanelHeight()) * 100;
+ const mpx = mp[0] / B.props.PanelWidth();
+ const mpy = mp[1] / B.props.PanelHeight();
+ if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
}
}
@@ -140,8 +145,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined;
const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
- const adiv = (acont.length ? acont[0] : A.ContentDiv);
- const bdiv = (bcont.length ? bcont[0] : B.ContentDiv);
+ const adiv = acont.length ? acont[0] : A.ContentDiv;
+ const bdiv = bcont.length ? bcont[0] : B.ContentDiv;
for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return;
for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return;
const a = adiv.getBoundingClientRect();
@@ -175,18 +180,29 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
render() {
if (!this.renderData) return (null);
+
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
LinkManager.currentLink = this.props.LinkDocs[0];
- const linkRelationship = StrCast(LinkManager.currentLink?.linkRelationship); //get string representing relationship
+ const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship
const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
const linkColorList = Doc.UserDoc().linkColorList as List<string>;
+ const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>;
+ const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
+
+ const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
+
//access stroke color using index of the relationship in the color list (default black)
- const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)];
+ const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex];
+
+ //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
+ //thickness varies linearly from 3px to 12px for increasing link count
+ const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px";
+
return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }}
+ <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, /*strokeDasharray: "2 2",*/ stroke, strokeWidth }}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
{textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
- {StrCast(this.props.LinkDocs[0].description)}
+ {Field.toString(this.props.LinkDocs[0].description as any as Field)}
</text>}
</>);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index e812064b7..dacbb3508 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,30 +1,17 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc } from "../../../../fields/Doc";
import { Id } from "../../../../fields/FieldSymbols";
-import { Utils } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
-import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { LinkManager } from "../../../util/LinkManager";
@observer
export class CollectionFreeFormLinksView extends React.Component {
@computed get uniqueConnections() {
- const connections = DocumentManager.Instance.LinkedDocumentViews
- .filter(c => c.a.props.Document.type === DocumentType.LINK || c.b.props.Document.type === DocumentType.LINK)
- .reduce((drawnPairs, connection) => {
- const matchingPairs = drawnPairs.filter(pair => connection.a === pair.a && connection.b === pair.b);
- matchingPairs.forEach(drawnPair => drawnPair.l.add(connection.l));
- if (!matchingPairs.length) drawnPairs.push({ a: connection.a, b: connection.b, l: new Set<Doc>([connection.l]) });
- return drawnPairs;
- }, [] as { a: DocumentView, b: DocumentView, l: Set<Doc> }[]);
- const set = new Map<Doc, { a: DocumentView, b: DocumentView, l: Doc[] }>();
- connections.map(c => !set.has(Array.from(c.l)[0]) && set.set(Array.from(c.l)[0], { a: c.a, b: c.b, l: Array.from(c.l) }));
- return Array.from(set.values()).map(c => <CollectionFreeFormLinkView key={c.l[0][Id]} A={c.a} B={c.b} LinkDocs={c.l} />);
+ return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)).map(c =>
+ <CollectionFreeFormLinkView key={c.l[Id]} A={c.a} B={c.b} LinkDocs={[c.l]} />
+ );
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fb949a36d..7dcd63b80 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { DateField } from "../../../../fields/DateField";
@@ -50,7 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import Color = require("color");
+import { ColorScheme } from "../../../util/SettingsManager";
export const panZoomSchema = createSchema({
_panX: "number",
@@ -183,7 +183,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
if (newBox instanceof Doc) {
- if (retVal = this.props.addDocument?.(newBox) || false) {
+ if (retVal = (this.props.addDocument?.(newBox) || false)) {
this.bringToFront(newBox);
this.updateCluster(newBox);
}
@@ -209,25 +209,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return retVal;
}
- updateGroupBounds = () => {
- if (!this.props.Document._isGroup) return;
- const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
- const cbounds = aggregateBounds(clist, 0, 0);
- const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
- const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
- const pbounds = {
- x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
- r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1]
- };
-
- this.layoutDoc._width = (pbounds.r - pbounds.x);
- this.layoutDoc._height = (pbounds.b - pbounds.y);
- this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
- this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
- this.layoutDoc.x = pbounds.x;
- this.layoutDoc.y = pbounds.y;
- }
-
isCurrent(doc: Doc) {
const dispTime = NumCast(doc._timecodeToShow, -1);
const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
@@ -269,8 +250,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
!StrListCast(d._layerTags).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
- this.updateGroupBounds();
-
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
return true;
}
@@ -510,7 +489,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const points = ge.points;
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points,
- { title: "ink stroke", x: B.x - Number(ActiveInkWidth()) / 2, y: B.y - Number(ActiveInkWidth()) / 2, _width: B.width + Number(ActiveInkWidth()), _height: B.height + Number(ActiveInkWidth()) });
+ { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() });
this.addDocument(inkDoc);
e.stopPropagation();
break;
@@ -832,14 +811,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) {
if (!this.isAnnotationOverlay && clamp) {
// this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
- const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
- const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "") && this.childSizeProvider(doc, "")).
- map(doc => ({ ...this.childDataProvider(doc, ""), ...this.childSizeProvider(doc, "") }));
+ const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc);
+ const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") }))
+ .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! }));
if (measuredDocs.length) {
- const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
+ const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content
({
- xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
- yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+ xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
+ yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }
})
, {
xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
@@ -923,7 +902,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
HistoryUtil.pushState(state);
}
}
- SelectionManager.DeselectAll();
+ if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
+ SelectionManager.DeselectAll();
+ }
if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined) {
this.props.focus(doc, options);
} else {
@@ -1052,9 +1033,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
freezeDimensions={this.props.childFreezeDimensions}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
- dontRegisterView={this.props.dontRegisterView}
+ showTitle={this.props.childShowTitle}
+ dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
- (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
+ (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
/>;
@@ -1123,10 +1105,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
+ childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
+ return this._layoutPoolData.get(doc[Id] + (replica || ""));
+ }
childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
return this._layoutPoolData.get(doc[Id] + (replica || ""));
}.bind(this));
+ childSizeProviderUnmemoized = (doc: Doc, replica: string) => {
+ return this._layoutSizeData.get(doc[Id] + (replica || ""));
+ }
childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
return this._layoutSizeData.get(doc[Id] + (replica || ""));
}.bind(this));
@@ -1235,6 +1223,30 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
{ fireImmediately: true, name: "doLayout" });
this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
+
+ this._disposers.groupBounds = reaction(() => {
+ if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
+ return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
+ }
+ return undefined;
+ },
+ (cbounds) => {
+ if (cbounds) {
+ const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
+ const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+ const pbounds = {
+ x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
+ r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1]
+ };
+ this.layoutDoc._width = (pbounds.r - pbounds.x);
+ this.layoutDoc._height = (pbounds.b - pbounds.y);
+ this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
+ this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+ this.layoutDoc.x = pbounds.x;
+ this.layoutDoc.y = pbounds.y;
+ }
+ }, { fireImmediately: true });
}
componentWillUnmount() {
@@ -1326,8 +1338,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
!options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
const mores = ContextMenu.Instance.findByDescription("More...");
const moreItems = mores && "subitems" in mores ? mores.subitems : [];
- moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
- moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+ if (!Doc.UserDoc().noviceMode) {
+ moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) });
+ moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+ }
!mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
}
@@ -1399,6 +1413,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
chooseGridSpace = (gridSpace: number): number => {
+ if (!this.zoomScaling()) return 50;
const divisions = this.props.PanelWidth() / this.zoomScaling() / gridSpace + 3;
return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
}
@@ -1410,6 +1425,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const renderGridSpace = gridSpace * this.zoomScaling();
const w = this.props.PanelWidth() + 2 * renderGridSpace;
const h = this.props.PanelHeight() + 2 * renderGridSpace;
+ const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
ref={(el) => {
const ctx = el?.getContext('2d');
@@ -1420,7 +1436,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
ctx.clearRect(0, 0, w, h);
if (ctx) {
- ctx.strokeStyle = "rgba(0, 0, 0, 0.5)";
+ ctx.strokeStyle = strokeStyle;
ctx.beginPath();
for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
ctx.moveTo(x, Cy - h);
@@ -1457,7 +1473,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
<div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}>
- {this.layoutDoc["_backgroundGrid-show"] ? this.backgroundGrid : (null)}
+ {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
<CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
transform={this.contentTransform}
@@ -1483,6 +1499,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return wscale < hscale ? wscale : hscale;
}
+ private groupDropDisposer?: DragManager.DragDropDisposer;
+ protected createGroupEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this.groupDropDisposer?.();
+ if (ele) {
+ this.groupDropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this));
+ }
+ }
+
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
@@ -1526,7 +1550,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
</div>}
{this.props.Document._isGroup && SnappingManager.GetIsDragging() && (this.ChildDrag || this.props.layerProvider?.(this.props.Document) === false) ?
- <div className="collectionFreeForm-groupDropper" ref={this.createDashEventsTarget} style={{
+ <div className="collectionFreeForm-groupDropper" ref={this.createGroupEventsTarget} style={{
width: this.ChildDrag ? "10000" : "100%",
height: this.ChildDrag ? "10000" : "100%",
left: this.ChildDrag ? "-5000" : 0,
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index cedeb1112..1f59f9732 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -3,20 +3,22 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { observer } from "mobx-react";
import { unimplementedFunction } from "../../../../Utils";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { SelectionManager } from "../../../util/SelectionManager";
import { AntimodeMenu, AntimodeMenuProps } from "../../AntimodeMenu";
@observer
export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
static Instance: MarqueeOptionsMenu;
- public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction;
public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
-
+ public isShown = () => this._opacity > 0;
constructor(props: Readonly<{}>) {
super(props);
@@ -26,42 +28,52 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> {
render() {
const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 19, transform: 'translate(-2px, -2px)' }} />;
const buttons = [
- <Tooltip key="group" title={<><div className="dash-tooltip">Create a Collection</div></>} placement="bottom">
+ <Tooltip key="collect" title={<div className="dash-tooltip">Create a Collection</div>} placement="bottom">
<button
className="antimodeMenu-button"
onPointerDown={this.createCollection}>
<FontAwesomeIcon icon="object-group" size="lg" />
</button>
</Tooltip>,
- <Tooltip key="summarize" title={<><div className="dash-tooltip">Summarize Documents</div></>} placement="bottom">
+ <Tooltip key="group" title={<div className="dash-tooltip">Create a Grouping</div>} placement="bottom">
<button
className="antimodeMenu-button"
- onPointerDown={this.summarize}>
- <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
+ onPointerDown={e => this.createCollection(e, true)}>
+ <FontAwesomeIcon icon="layer-group" size="lg" />
</button>
</Tooltip>,
- <Tooltip key="delete" title={<><div className="dash-tooltip">Delete Documents</div></>} placement="bottom">
+ <Tooltip key="summarize" title={<div className="dash-tooltip">Summarize Documents</div>} placement="bottom">
<button
className="antimodeMenu-button"
- onPointerDown={this.delete}>
- <FontAwesomeIcon icon="trash-alt" size="lg" />
+ onPointerDown={this.summarize}>
+ <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
</button>
</Tooltip>,
- <Tooltip key="inkToText" title={<><div className="dash-tooltip">Change to Text</div></>} placement="bottom">
+ <Tooltip key="delete" title={<div className="dash-tooltip">Delete Documents</div>} placement="bottom">
<button
className="antimodeMenu-button"
- onPointerDown={this.inkToText}>
- <FontAwesomeIcon icon="font" size="lg" />
+ onPointerDown={this.delete}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" />
</button>
</Tooltip>,
- <Tooltip key="pinWithView" title={<><div className="dash-tooltip">Pin with selected region</div></>} placement="bottom">
+ <Tooltip key="pinWithView" title={<div className="dash-tooltip">Pin with selected region</div>} placement="bottom">
<button
className="antimodeMenu-button"
onPointerDown={this.pinWithView}>
- <>{presPinWithViewIcon}</>
+ {presPinWithViewIcon}
</button>
</Tooltip>,
];
+ if (false && !SelectionManager.Views().some(v => v.props.Document.type !== DocumentType.INK)) {
+ buttons.push(
+ <Tooltip key="inkToText" title={<div className="dash-tooltip">Change to Text</div>} placement="bottom">
+ <button
+ className="antimodeMenu-button"
+ onPointerDown={this.inkToText}>
+ <FontAwesomeIcon icon="font" size="lg" />
+ </button>
+ </Tooltip>);
+ }
return this.getElement(buttons);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 81f6307d1..24a7d77e0 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -646,8 +646,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
render() {
return <div className="marqueeView"
style={{
- overflow: //(!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" :
- StrCast(this.props.Document._overflow),
+ overflow: StrCast(this.props.Document._overflow),
cursor: "hand"
}}
onDragOver={e => e.preventDefault()}
diff --git a/src/client/views/collections/collectionFreeForm/index.ts b/src/client/views/collections/collectionFreeForm/index.ts
new file mode 100644
index 000000000..702dc8d42
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/index.ts
@@ -0,0 +1,7 @@
+export * from "./CollectionFreeFormLayoutEngines";
+export * from "./CollectionFreeFormLinkView";
+export * from "./CollectionFreeFormLinksView";
+export * from "./CollectionFreeFormRemoteCursors";
+export * from "./CollectionFreeFormView";
+export * from "./MarqueeOptionsMenu";
+export * from "./MarqueeView"; \ No newline at end of file
diff --git a/src/client/views/collections/collectionGrid/index.ts b/src/client/views/collections/collectionGrid/index.ts
new file mode 100644
index 000000000..be5d5667a
--- /dev/null
+++ b/src/client/views/collections/collectionGrid/index.ts
@@ -0,0 +1,2 @@
+export * from "./Grid";
+export * from "./CollectionGridView"; \ No newline at end of file
diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
index 46e40489b..8fe804466 100644
--- a/src/client/views/collections/CollectionLinearView.scss
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss
@@ -1,15 +1,31 @@
-@import "../global/globalCssVariables";
-@import "../_nodeModuleOverrides";
+@import "../../global/globalCssVariables";
+@import "../../_nodeModuleOverrides";
.collectionLinearView-outer {
overflow: visible;
height: 100%;
pointer-events: none;
+ &.true {
+ padding-left: 5px;
+ padding-right: 5px;
+ border-left: $standard-border;
+ background-color: $medium-blue-alt;
+ }
+
+ >input:not(:checked)~&.true {
+ background-color: transparent;
+ }
+
.collectionLinearView {
display: flex;
height: 100%;
align-items: center;
+ gap: 5px;
+
+ .collectionView {
+ overflow: visible !important;
+ }
>span {
background: $dark-gray;
@@ -41,7 +57,7 @@
}
.bottomPopup-descriptions {
- cursor:pointer;
+ cursor: pointer;
display: inline;
white-space: nowrap;
padding-left: 8px;
@@ -54,7 +70,7 @@
}
.bottomPopup-exit {
- cursor:pointer;
+ cursor: pointer;
display: inline;
white-space: nowrap;
margin-right: 10px;
@@ -67,29 +83,26 @@
}
>label {
- margin-top: "auto";
- margin-bottom: "auto";
- background: $dark-gray;
- color: $white;
- display: inline-block;
- border-radius: 18px;
- font-size: 12.5px;
- width: 18px;
- height: 18px;
- margin-top: auto;
- margin-bottom: auto;
- margin-right: 3px;
+ pointer-events: all;
cursor: pointer;
+ background-color: $medium-blue;
+ padding: 5;
+ border-radius: 2px;
+ height: 25;
+ min-width: 25;
+ margin: 0;
+ color: $white;
+ display: flex;
+ font-weight: 100;
+ width: fit-content;
transition: transform 0.2s;
- }
-
- label p {
- padding-left: 5px;
- }
+ align-items: center;
+ justify-content: center;
+ transition: 0.2s;
- label:hover {
- background: $medium-gray;
- transform: scale(1.15);
+ &:hover{
+ filter: brightness(0.85);
+ }
}
>input {
@@ -110,13 +123,11 @@
display: flex;
opacity: 1;
position: relative;
- margin-top: auto;
.collectionLinearView-docBtn,
.collectionLinearView-docBtn-scalable {
position: relative;
margin: auto;
- margin-left: 3px;
transform-origin: center 80%;
}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
new file mode 100644
index 000000000..18a715edf
--- /dev/null
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -0,0 +1,225 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
+import { documentSchema } from '../../../../fields/documentSchemas';
+import { Id } from '../../../../fields/FieldSymbols';
+import { makeInterface } from '../../../../fields/Schema';
+import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils';
+import { DragManager } from '../../../util/DragManager';
+import { Transform } from '../../../util/Transform';
+import { Colors, Shadows } from '../../global/globalEnums';
+import { DocumentLinksButton } from '../../nodes/DocumentLinksButton';
+import { DocumentView } from '../../nodes/DocumentView';
+import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup';
+import { StyleProp } from '../../StyleProvider';
+import { CollectionSubView } from '../CollectionSubView';
+import { CollectionViewType } from '../CollectionView';
+import "./CollectionLinearView.scss";
+
+
+type LinearDocument = makeInterface<[typeof documentSchema,]>;
+const LinearDocument = makeInterface(documentSchema);
+
+@observer
+export class CollectionLinearView extends CollectionSubView(LinearDocument) {
+ @observable public addMenuToggle = React.createRef<HTMLInputElement>();
+ @observable private _selectedIndex = -1;
+ private _dropDisposer?: DragManager.DragDropDisposer;
+ private _widthDisposer?: IReactionDisposer;
+ private _selectedDisposer?: IReactionDisposer;
+
+ componentWillUnmount() {
+ this._dropDisposer?.();
+ this._widthDisposer?.();
+ this._selectedDisposer?.();
+ this.childLayoutPairs.map((pair, ind) => ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log));
+ }
+
+ componentDidMount() {
+ this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10),
+ width => this.childDocs.length && (this.layoutDoc._width = width),
+ { fireImmediately: true }
+ );
+
+ this._selectedDisposer = reaction(
+ () => NumCast(this.layoutDoc.selectedIndex),
+ (i) => runInAction(() => {
+ this._selectedIndex = i;
+ let selected: any = undefined;
+ this.childLayoutPairs.map(async (pair, ind) => {
+ const isSelected = this._selectedIndex === ind;
+ if (isSelected) {
+ selected = pair;
+ }
+ else {
+ ScriptCast(pair.layout.proto?.onPointerUp)?.script.run({ this: pair.layout.proto }, console.log);
+ }
+ });
+ if (selected && selected.layout) {
+ ScriptCast(selected.layout.proto?.onPointerDown)?.script.run({ this: selected.layout.proto }, console.log);
+ }
+ }),
+ { fireImmediately: true }
+ );
+ }
+ protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
+ this._dropDisposer && this._dropDisposer();
+ if (ele) {
+ this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+ }
+ }
+
+ dimension = () => NumCast(this.rootDoc._height); // 2 * the padding
+ getTransform = (ele: Opt<HTMLDivElement>) => {
+ if (!ele) return Transform.Identity();
+ const { scale, translateX, translateY } = Utils.GetScreenTransform(ele);
+ return new Transform(-translateX, -translateY, 1);
+ }
+
+ @action
+ exitLongLinks = () => {
+ if (DocumentLinksButton.StartLink) {
+ if (DocumentLinksButton.StartLink.Document) {
+ action((e: React.PointerEvent<HTMLDivElement>) => {
+ Doc.UnBrushDoc(DocumentLinksButton.StartLink?.Document as Doc);
+ });
+ }
+ }
+ DocumentLinksButton.StartLink = undefined;
+ DocumentLinksButton.StartLinkView = undefined;
+ }
+
+ @action
+ changeDescriptionSetting = () => {
+ if (LinkDescriptionPopup.showDescriptions) {
+ if (LinkDescriptionPopup.showDescriptions === "ON") {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ } else {
+ LinkDescriptionPopup.showDescriptions = "ON";
+ }
+ } else {
+ LinkDescriptionPopup.showDescriptions = "OFF";
+ LinkDescriptionPopup.descriptionPopup = false;
+ }
+ }
+
+ myContextMenu = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+
+ getDisplayDoc = (doc: Doc) => {
+ const nested = doc._viewType === CollectionViewType.Linear;
+ const hidden = doc.hidden === true;
+
+ let dref: Opt<HTMLDivElement>;
+ const docXf = () => this.getTransform(dref);
+ // const scalable = pair.layout.onClick || pair.layout.onDragStart;
+ return hidden ? (null) : <div className={`collectionLinearView-docBtn`} key={doc[Id]} ref={r => dref = r || undefined}
+ style={{
+ pointerEvents: "all",
+ width: nested ? undefined : NumCast(doc._width),
+ height: nested ? undefined : NumCast(doc._height),
+ marginLeft: !nested ? 2.5 : 0,
+ marginRight: !nested ? 2.5 : 0,
+ // width: NumCast(pair.layout._width),
+ // height: NumCast(pair.layout._height),
+ }} >
+ <DocumentView
+ Document={doc}
+ isContentActive={this.props.isContentActive}
+ isDocumentActive={returnTrue}
+ addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
+ addDocTab={this.props.addDocTab}
+ pinToPres={emptyFunction}
+ rootSelected={this.props.isSelected}
+ removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={docXf}
+ PanelWidth={nested ? doc[WidthSym] : this.dimension}
+ PanelHeight={nested ? doc[HeightSym] : this.dimension}
+ renderDepth={this.props.renderDepth + 1}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ layerProvider={this.props.layerProvider}
+ docViewPath={returnEmptyDoclist}
+ whenChildContentsActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ </div>;
+ }
+
+ render() {
+ const guid = Utils.GenerateGuid(); // Generate a unique ID to use as the label
+ const flexDir: any = StrCast(this.Document.flexDirection); // Specify direction of linear view content
+ const flexGap: number = NumCast(this.Document.flexGap); // Specify the gap between linear view content
+ const expandable: boolean = BoolCast(this.props.Document.linearViewExpandable); // Specify whether it is expandable or not
+ const floating: boolean = BoolCast(this.props.Document.linearViewFloating); // Specify whether it is expandable or not
+
+ const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+ const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+ const icon: string = StrCast(this.props.Document.icon); // Menu opener toggle
+ const menuOpener = <label htmlFor={`${guid}`}
+ style={{
+ boxShadow: floating ? Shadows.STANDARD_SHADOW : undefined,
+ color: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.BLACK,
+ backgroundColor: backgroundColor === color ? "black" : BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : Colors.LIGHT_GRAY
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <div className="collectionLinearView-menuOpener">
+ {BoolCast(this.layoutDoc.linearViewIsExpanded) ? icon ? icon : <FontAwesomeIcon icon={"minus"} /> : icon ? icon : <FontAwesomeIcon icon={"plus"} />}
+ </div>
+ </label>;
+
+ return <div className={`collectionLinearView-outer ${this.layoutDoc.linearViewSubMenu}`} style={{ backgroundColor: BoolCast(this.layoutDoc.linearViewIsExpanded) ? undefined : "transparent" }}>
+ <div className="collectionLinearView" ref={this.createDashEventsTarget}
+ onContextMenu={this.myContextMenu} >
+ {!expandable ? (null) : <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close" : "Open"}</div></>} placement="top">
+ {menuOpener}
+ </Tooltip>}
+ <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
+ onChange={action(() => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
+
+ <div className="collectionLinearView-content"
+ style={{
+ height: this.dimension(),
+ flexDirection: flexDir,
+ gap: flexGap
+ }}>
+ {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
+ </div>
+ {DocumentLinksButton.StartLink && StrCast(this.layoutDoc.title) === "docked buttons" ? <span className="bottomPopup-background" style={{
+ pointerEvents: "all"
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <span className="bottomPopup-text" >
+ Creating link from: <b>{DocumentLinksButton.AnnotationId ? "Annotation in " : " "} {StrCast(DocumentLinksButton.StartLink.title).length < 51 ? DocumentLinksButton.StartLink.title : StrCast(DocumentLinksButton.StartLink.title).slice(0, 50) + '...'}</b>
+ </span>
+
+ <Tooltip title={<><div className="dash-tooltip">{"Toggle description pop-up"} </div></>} placement="top">
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+ Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ </span>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Exit linking mode</div></>} placement="top">
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+ Stop
+ </span>
+ </Tooltip>
+
+ </span> : null}
+ </div>
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionLinear/index.ts b/src/client/views/collections/collectionLinear/index.ts
new file mode 100644
index 000000000..ff73e14ae
--- /dev/null
+++ b/src/client/views/collections/collectionLinear/index.ts
@@ -0,0 +1 @@
+export * from "./CollectionLinearView"; \ No newline at end of file
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index 9a3ef1422..a439a7998 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -120,7 +120,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
- // console.log("click cell");
let url: string;
if (url = StrCast(this.props.rowProps.row.href)) {
// opens up the the doc in a new window, blurring the old one
@@ -338,12 +337,12 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
}
const script = CompileScript(inputSansCommas, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = value.length !== value.length || value.length - 2 !== value.length;
+ const changeMade = value.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
// handle booleans
} else if (inputIsBool) {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
- const changeMade = value.length !== value.length || value.length - 2 !== value.length;
+ const changeMade = value.length - 2 !== value.length;
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
}
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index 52865eba6..074074bc5 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -1,18 +1,17 @@
import React = require("react");
-import { IconProp, library } from "@fortawesome/fontawesome-svg-core";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, Opt } from "../../../../fields/Doc";
+import { Doc, DocListCast, Opt, StrListCast } from "../../../../fields/Doc";
import { listSpec } from "../../../../fields/Schema";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../../fields/SchemaHeaderField";
import { ScriptField } from "../../../../fields/ScriptField";
import { Cast, StrCast } from "../../../../fields/Types";
import { undoBatch } from "../../../util/UndoManager";
-import { SearchBox } from "../../search/SearchBox";
+import { CollectionView } from "../CollectionView";
import { ColumnType } from "./CollectionSchemaView";
import "./CollectionSchemaView.scss";
-import { CollectionView } from "../CollectionView";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -27,9 +26,9 @@ export interface AddColumnHeaderProps {
export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
// the button that allows the user to add a column
render() {
- return (
- <button className="add-column" onClick={() => this.props.createColumn()}><FontAwesomeIcon icon="plus" size="sm" /></button>
- );
+ return <button className="add-column" onClick={() => this.props.createColumn()}>
+ <FontAwesomeIcon icon="plus" size="sm" />
+ </button>;
}
}
@@ -191,7 +190,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
{this.renderSorting()}
{this.renderColors()}
<div className="collectionSchema-headerMenu-group">
- <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Delete Column</button>
+ <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Hide Column</button>
</div>
</>
}
@@ -234,7 +233,7 @@ export interface KeysDropdownProps {
@observer
export class KeysDropdown extends React.Component<KeysDropdownProps> {
@observable private _key: string = this.props.keyValue;
- @observable private _searchTerm: string = this.props.keyValue;
+ @observable private _searchTerm: string = this.props.keyValue + ":";
@observable private _isOpen: boolean = false;
@observable private _node: HTMLDivElement | null = null;
@observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef();
@@ -326,11 +325,11 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
|| ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null);
return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm));
}
- @action
- renderOptions = (): JSX.Element[] | JSX.Element => {
+
+ @computed get renderOptions() {
if (!this._isOpen) {
this.defaultMenuHeight = 0;
- return <></>;
+ return (null);
}
const options = this.showKeys.map(key => {
return <div key={key} className="key-option" style={{
@@ -373,30 +372,25 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
return options;
}
- docSafe: Doc[] = [];
+ @computed get docSafe() { return DocListCast(this.props.dataDoc?.[this.props.fieldKey]); }
- @action
- renderFilterOptions = (): JSX.Element[] | JSX.Element => {
+ @computed get renderFilterOptions() {
if (!this._isOpen || !this.props.dataDoc) {
this.defaultMenuHeight = 0;
- return <></>;
+ return (null);
}
const keyOptions: string[] = [];
const colpos = this._searchTerm.indexOf(":");
const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length);
- if (this.docSafe.length === 0) {
- this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]);
- }
- const docs = this.docSafe;
- docs.forEach((doc) => {
+ this.docSafe.forEach(doc => {
const key = StrCast(doc[this._key]);
if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") {
keyOptions.push(key);
}
});
- const filters = Cast(this.props.Document._docFilters, listSpec("string"));
- if (filters === undefined || filters.length === 0 || filters.some(filter => filter.split(":")[0] === this._key) === false) {
+ const filters = StrListCast(this.props.Document._docFilters);
+ if (filters.some(filter => filter.split(":")[0] === this._key) === false) {
this.props.col.setColor("rgb(241, 239, 235)");
this.closeResultsVisibility = "none";
}
@@ -408,24 +402,28 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
const options = keyOptions.map(key => {
let bool = false;
if (filters !== undefined) {
- const ind = filters.findIndex(filter => filter.split(":")[0] === key);
+ const ind = filters.findIndex(filter => filter.split(":")[1] === key);
const fields = ind === -1 ? undefined : filters[ind].split(":");
- bool = fields ? fields[1] === "check" : false;
+ bool = fields ? fields[2] === "check" : false;
}
return <div key={key} className="key-option" style={{
- border: "1px solid lightgray", paddingLeft: 5, textAlign: "left",
+ paddingLeft: 5, textAlign: "left",
width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white",
}}
>
<input type="checkbox"
onPointerDown={e => e.stopPropagation()}
onClick={e => e.stopPropagation()}
- onChange={(e) => {
- e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, "remove");
- e.target.checked === true ? this.closeResultsVisibility = "contents" : console.log("");
- e.target.checked === true ? this.props.col.setColor("green") : this.updateFilter();
- e.target.checked === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, "remove");
- }}
+ onChange={action(e => {
+ if (e.target.checked) {
+ Doc.setDocFilter(this.props.Document, this._key, key, "check");
+ this.closeResultsVisibility = "contents";
+ this.props.col.setColor("green");
+ } else {
+ Doc.setDocFilter(this.props.Document, this._key, key, "remove");
+ this.updateFilter();
+ }
+ })}
checked={bool}
/>
<span style={{ paddingLeft: 4 }}>
@@ -472,11 +470,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
removeFilters = (e: React.PointerEvent): void => {
const keyOptions: string[] = [];
- if (this.docSafe.length === 0 && this.props.dataDoc) {
- this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]);
- }
- const docs = this.docSafe;
- docs.forEach((doc) => {
+ this.docSafe.forEach(doc => {
const key = StrCast(doc[this._key]);
if (keyOptions.includes(key) === false) {
keyOptions.push(key);
@@ -489,12 +483,10 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
}
render() {
return (
- <div style={{ display: "flex" }} ref={this.setNode}>
- <FontAwesomeIcon onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} />
-
- {/* <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => {
- runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen })
- }} /> */}
+ <div style={{ display: "flex", width: '100%', alignContent: 'center', alignItems: 'center' }} ref={this.setNode}>
+ <div className="schema-icon" onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }}>
+ <FontAwesomeIcon icon={this.props.icon} size="lg" style={{ display: "inline" }} />
+ </div>
<div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}>
<input className="keys-search" style={{ width: "100%" }}
@@ -509,7 +501,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
{!this._isOpen ? (null) : <div className="keys-options-wrapper" style={{
width: this.props.width, maxWidth: this.props.width, height: "auto",
}}>
- {this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()}
+ {this._searchTerm.includes(":") ? this.renderFilterOptions : this.renderOptions}
</div>}
</div >
</div>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
index f48906ba5..0e19ef3d9 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableRow.tsx
@@ -134,9 +134,9 @@ export class MovableRow extends React.Component<MovableRowProps> {
<div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
<ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} >
<div className="row-dragger">
- <div className="row-option" style={{ left: 5 }} onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
- <div className="row-option" style={{ cursor: "grab", left: 25 }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
- <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
+ <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div>
+ <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div>
+ <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "add:right")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div>
</div>
{children}
</ReactTableDefaults.TrComponent>
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index c17b79638..b64e9dac1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -108,9 +108,7 @@
}
.rt-th {
padding: 0;
- border: solid lightgray;
- border-width: 0 1px;
- border-bottom: 2px solid lightgray;
+ border-left: solid 1px $light-gray;
}
}
.rt-th {
@@ -220,6 +218,8 @@
}
}
+
+
.collectionSchemaView-header {
height: 100%;
color: gray;
@@ -234,6 +234,15 @@ button.add-column {
width: 28px;
}
+.collectionSchemaView-menuOptions-wrapper {
+ background: rgb(241, 239, 235);
+ display: flex;
+ cursor: default;
+ height: 100%;
+ align-content: center;
+ align-items: center;
+}
+
.collectionSchema-header-menuOptions {
color: black;
width: 180px;
@@ -279,6 +288,9 @@ button.add-column {
width: 10px;
}
}
+
+
+
.keys-dropdown {
position: relative;
//width: 100%;
@@ -294,26 +306,7 @@ button.add-column {
font-weight: normal;
}
}
- .keys-options-wrapper {
- width: 100%;
- max-height: 150px;
- overflow-y: scroll;
- position: absolute;
- top: 28px;
- box-shadow: 0 10px 16px rgba(0, 0, 0, 0.1);
- background-color: white;
- .key-option {
- background-color: white;
- border: 1px solid lightgray;
- padding: 2px 3px;
- &:not(:first-child) {
- border-top: 0;
- }
- &:hover {
- background-color: $light-gray;
- }
- }
- }
+
}
.columnMenu-colors {
display: flex;
@@ -332,11 +325,53 @@ button.add-column {
}
}
+.schema-icon {
+ cursor: pointer;
+ width: 25px;
+ height: 25px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ align-content: center;
+ background-color: $medium-blue;
+ color: white;
+ margin-right: 5px;
+ font-size: 10px;
+ border-radius: 3px;
+
+}
+
+.keys-options-wrapper {
+ position: absolute;
+ text-align: left;
+ height: fit-content;
+ top: 100%;
+ z-index: 21;
+ background-color: #ffffff;
+ box-shadow: 0px 3px 4px rgba(0,0,0,30%);
+ padding: 1px;
+ .key-option {
+ cursor: pointer;
+ color: #000000;
+ width: 100%;
+ height: 25px;
+ font-weight: 400;
+ display: flex;
+ justify-content: left;
+ align-items: center;
+ padding-left: 5px;
+ &:hover {
+ background-color: $light-gray;
+ }
+ }
+}
+
.collectionSchema-row {
height: 100%;
background-color: white;
&.row-focused .rt-td {
- background-color: #bfffc0; //$light-gray;
+ background-color: $light-blue; //$light-gray;
+ overflow: visible;
}
&.row-wrapped {
.rt-td {
@@ -345,39 +380,40 @@ button.add-column {
}
.row-dragger {
display: flex;
- justify-content: space-around;
- //flex: 50 0 auto;
- width: 0;
- max-width: 50px;
- //height: 100%;
+ justify-content: space-evenly;
+ width: 58px;
+ position: absolute;
+ /* max-width: 50px; */
min-height: 30px;
align-items: center;
color: lightgray;
background-color: white;
transition: color 0.1s ease;
.row-option {
- // padding: 5px;
+ color: black;
cursor: pointer;
- position: absolute;
+ position: relative;
transition: color 0.1s ease;
display: flex;
flex-direction: column;
justify-content: center;
z-index: 2;
+ border-radius: 3px;
+ padding: 3px;
&:hover {
- color: gray;
+ background-color: $light-gray;
}
}
}
.collectionSchema-row-wrapper {
&.row-above {
- border-top: 1px solid red;
+ border-top: 1px solid $medium-blue;
}
&.row-below {
- border-bottom: 1px solid red;
+ border-bottom: 1px solid $medium-blue;
}
&.row-inside {
- border: 1px solid red;
+ border: 2px dashed $medium-blue;
}
.row-dragging {
background-color: blue;
@@ -390,28 +426,37 @@ button.add-column {
height: unset;
}
+.collectionSchemaView-cellContents {
+ width: 100%;
+}
+
.collectionSchemaView-cellWrapper {
+ display: flex;
height: 100%;
- padding: 4px;
text-align: left;
padding-left: 19px;
position: relative;
+ align-items: center;
+ align-content: center;
&:focus {
outline: none;
}
&.editing {
padding: 0;
+ box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
+ transform: scale(1.1);
+ z-index: 40;
input {
outline: 0;
border: none;
- background-color: rgb(255, 217, 217);
+ background-color: $white;
width: 100%;
- height: 100%;
- padding: 2px 3px;
+ height: fit-content;
min-height: 26px;
}
}
&.focused {
+ overflow: hidden;
&.inactive {
border: none;
}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index fed64b620..b89246489 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from "mobx";
+import { action, computed, observable, untracked, trace } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import { Resize } from "react-table";
@@ -337,8 +337,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
{this.renderTypes(this._col)}
{this.renderColors(this._col)}
<div className="collectionSchema-headerMenu-group">
- <button onClick={() => { this.deleteColumn(this._col.heading); }}
- >Delete Column</button>
+ <button onClick={() => { this.deleteColumn(this._col.heading); }}>
+ Hide Column
+ </button>
</div>
</div>;
}
@@ -353,7 +354,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action setFocused = (doc: Doc) => this._focusedTable = doc;
@action setPreviewDoc = (doc: Opt<Doc>) => {
- SelectionManager.SelectSchemaView(this, doc);
+ SelectionManager.SelectSchemaViewDoc(doc);
this._previewDoc = doc;
}
diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx
index 631f623c6..bc5a9559f 100644
--- a/src/client/views/collections/collectionSchema/SchemaTable.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx
@@ -1,7 +1,7 @@
import React = require("react");
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, Column, ComponentPropsGetterR, Resize, SortingRule } from "react-table";
import "react-table/react-table.css";
@@ -194,10 +194,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
const header = <div className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}>
{keysDropdown}
- <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}>
+ <div onClick={e => this.changeSorting(col)} style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "pointer" }}>
<FontAwesomeIcon icon={sortIcon} size="lg" />
</div>
- {this.props.Document._chromeHidden || this.props.addDocument === returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>}
+ {/* {this.props.Document._chromeHidden || this.props.addDocument == returnFalse ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} */}
</div>;
return {
@@ -386,13 +386,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@undoBatch
@action
createColumn = () => {
- let index = 0;
- let found = this.props.columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
- while (found) {
- index++;
- found = this.props.columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
+ const newFieldName = (index: number) => `New field${index ? ` (${index})` : ""}`;
+ for (let index = 0; index < 100; index++) {
+ if (this.props.columns.findIndex(col => col.heading === newFieldName(index)) === -1) {
+ this.props.columns.push(new SchemaHeaderField(newFieldName(index), "#f1efeb"));
+ break;
+ }
}
- this.props.columns.push(new SchemaHeaderField(`New field ${index ? "(" + index + ")" : ""}`, "#f1efeb"));
}
@action
@@ -567,7 +567,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
<div className="collectionSchemaView-documentPreview" ref="overlay"
style={{
position: "absolute", width: 150, height: 150,
- background: "dimGray", display: "block", top: 0, left: 0,
+ background: "dimgray", display: "block", top: 0, left: 0,
transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)`
}} >
<DocumentView