diff options
Diffstat (limited to 'src')
18 files changed, 1928 insertions, 19 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4690d856d..d22a9d763 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -862,6 +862,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId); } + export function NoteTakingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.NoteTaking }, id, undefined, protoId); + } + export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Multicolumn }); } @@ -1245,7 +1249,7 @@ export namespace DocUtils { return ctor ? ctor(path, options) : undefined; } - export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false): void { + export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false, pivotField?: string, pivotValue?: string): void { !simpleMenu && ContextMenu.Instance.addItem({ description: "Quick Notes", subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({ @@ -1253,16 +1257,36 @@ export namespace DocUtils { event: undoBatch((args: { x: number, y: number }) => { const textDoc = Docs.Create.TextDocument("", { _width: 200, x, y, _autoHeight: note._autoHeight !== false, - title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) + title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1), }); textDoc.layoutKey = "layout_" + note.title; textDoc[textDoc.layoutKey] = note; + if (pivotField) { + textDoc[pivotField] = pivotValue + } docTextAdder(textDoc); }), icon: StrCast(note.icon) as IconProp })) as ContextMenuProps[], icon: "sticky-note" }); + const math: ContextMenuProps = ({ + description: ":Math", event: () => { + const created = Docs.Create.EquationDocument(); + if (created) { + created.author = Doc.CurrentUserEmail; + created.x = x; + created.y = y; + created.width = 300; + created.height = 35; + EquationBox.SelectOnLoad = created[Id]; + if (pivotField) { + created[pivotField] = pivotValue + } + docAdder?.(created); + } + }, icon: "calculator" + }); const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ description: ":" + StrCast(dragDoc.title).replace("Untitled ",""), event: undoBatch((args: { x: number, y: number }) => { @@ -1273,6 +1297,9 @@ export namespace DocUtils { newDoc.y = y; EquationBox.SelectOnLoad = newDoc[Id]; if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id]; + if (pivotField) { + newDoc[pivotField] = pivotValue + } docAdder?.(newDoc); } }), diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 9c6d9a108..14dce66f0 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -224,7 +224,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { const isInk = layoutField instanceof InkField; const isMap = this.selectedDoc?.type === DocumentType.MAP; const isCollection = this.selectedDoc?.type === DocumentType.COL; - const isStacking = this.selectedDoc?._viewType === CollectionViewType.Stacking; + //TODO: will likely need to create separate note-taking view type here + const isStacking = this.selectedDoc?._viewType === CollectionViewType.Stacking || this.selectedDoc?._viewType === CollectionViewType.NoteTaking; const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform; const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree; const isTabView = this.selectedTabView; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index faab2ed26..d7d6f8586 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1401,7 +1401,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { const type = PresBox.Instance.activeItem?.type; const viewType = PresBox.Instance.activeItem?._viewType; const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG; - const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking; + const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking || viewType === CollectionViewType.NoteTaking; return <div className="propertiesView" style={{ width: this.props.width }}> <div className="propertiesView-title" style={{ width: this.props.width }}> Presentation diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 35415ae4e..d4ef73557 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -103,6 +103,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps return allSorts; case StyleProp.DocContents: return undefined; case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey"; + // return doc = dragManager.dragDocument ? props.dragEffects.opacity??CastofOpacityonline94 : Cast()) + // same idea for background Color case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null)); case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton)); case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize))); @@ -126,7 +128,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps case StyleProp.TitleHeight: return 15; case StyleProp.BorderPath: return comicStyle() && props?.renderDepth && doc?.type !== DocumentType.INK ? { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, .08), width: 3 } : { path: undefined, width: 0 }; case StyleProp.JitterRotation: return comicStyle() ? random(-1, 1, NumCast(doc?.x), NumCast(doc?.y)) * ((props?.PanelWidth() || 0) > (props?.PanelHeight() || 0) ? 5 : 10) : 0; - case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || + case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || (doc?.type === DocumentType.RTF && !showTitle()?.includes("noMargin")) || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 668d82387..430c67add 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -279,6 +279,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu @computed get _freeform_commands() { return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } @computed get _stacking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _notetaking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _masonry_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _schema_commands() { return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } @computed get _doc_commands() { return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } @@ -290,6 +291,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu case CollectionViewType.Tree: return this._tree_commands; case CollectionViewType.Schema: return this._schema_commands; case CollectionViewType.Stacking: return this._stacking_commands; + case CollectionViewType.NoteTaking: return this._notetaking_commands; case CollectionViewType.Masonry: return this._stacking_commands; case CollectionViewType.Time: return this._freeform_commands; case CollectionViewType.Carousel: return this._freeform_commands; @@ -327,6 +329,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu case CollectionViewType.Invalid: case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />); case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />); + case CollectionViewType.NoteTaking: return (<CollectionNoteTakingViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />); @@ -497,7 +500,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu const size: number = PresBox.Instance?._selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; const activeDoc = presSelected ? PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.indexOf(presSelected) + 1] : PresBox.Instance?.childDocs[PresBox.Instance?.childDocs.length - 1]; - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking || targetDoc._viewType === CollectionViewType.NoteTaking) { const scroll = targetDoc._scrollTop; activeDoc.presPinView = true; activeDoc.presPinViewScroll = scroll; @@ -988,6 +991,126 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView } } +@observer +export class CollectionNoteTakingViewChrome extends React.Component<CollectionViewMenuProps> { + @observable private _currentKey: string = ""; + @observable private suggestions: string[] = []; + + get document() { return this.props.docView.props.Document; } + + @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; } + @computed get pivotField() { return StrCast(this.document._pivotField); } + + getKeySuggestions = async (value: string): Promise<string[]> => { + const val = value.toLowerCase(); + const docs = DocListCast(this.document[this.props.fieldKey]); + + if (Doc.UserDoc().noviceMode) { + if (docs instanceof Doc) { + const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || + key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || + (key[0].toUpperCase() === key[0] && key[0] !== "_")); + return keys.filter(key => key.toLowerCase().indexOf(val) > -1); + } else { + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || + key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || + (key[0]?.toUpperCase() === key[0] && key[0] !== "_")); + return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); + } + } + + if (docs instanceof Doc) { + return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); + } else { + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); + } + } + + @action + onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { + this._currentKey = newValue; + } + + getSuggestionValue = (suggestion: string) => suggestion; + + renderSuggestion = (suggestion: string) => { + return <p>{suggestion}</p>; + } + + onSuggestionFetch = async ({ value }: { value: string }) => { + const sugg = await this.getKeySuggestions(value); + runInAction(() => { + this.suggestions = sugg; + }); + } + + @action + onSuggestionClear = () => { + this.suggestions = []; + } + + @action + setValue = (value: string) => { + this.document._pivotField = value; + return true; + } + + @action toggleSort = () => { + this.document._columnsSort = + this.document._columnsSort === "descending" ? "ascending" : + this.document._columnsSort === "ascending" ? undefined : "descending"; + } + @action resetValue = () => { this._currentKey = this.pivotField; }; + + render() { + const doctype = this.props.docView.Document.type; + const isPres: boolean = (doctype === DocumentType.PRES); + return ( + isPres ? (null) : <div className="collectionStackingViewChrome-cont"> + <div className="collectionStackingViewChrome-pivotField-cont"> + <div className="collectionStackingViewChrome-pivotField-label"> + GROUP BY: + </div> + <div className="collectionStackingViewChrome-sortIcon" onClick={this.toggleSort} style={{ transform: `rotate(${this.descending ? "180" : "0"}deg)` }}> + <FontAwesomeIcon icon="caret-up" size="2x" color="white" /> + </div> + <div className="collectionStackingViewChrome-pivotField"> + <EditableView + GetValue={() => this.pivotField} + autosuggestProps={ + { + resetValue: this.resetValue, + value: this._currentKey, + onChange: this.onKeyChange, + autosuggestProps: { + inputProps: + { + value: this._currentKey, + onChange: this.onKeyChange + }, + getSuggestionValue: this.getSuggestionValue, + suggestions: this.suggestions, + alwaysRenderSuggestions: true, + renderSuggestion: this.renderSuggestion, + onSuggestionsFetchRequested: this.onSuggestionFetch, + onSuggestionsClearRequested: this.onSuggestionClear + } + }} + oneLine + SetValue={this.setValue} + contents={this.pivotField ? this.pivotField : "N/A"} + /> + </div> + </div> + </div> + ); + } +} + @observer export class CollectionSchemaViewChrome extends React.Component<CollectionViewMenuProps> { diff --git a/src/client/views/collections/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss new file mode 100644 index 000000000..a878033ed --- /dev/null +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -0,0 +1,492 @@ +@import "../global/globalCssVariables"; + +.collectionNoteTakingView-DocumentButtons { + display: flex; + justify-content: space-between; + margin: auto; +} + +.collectionNoteTakingView-addDocumentButton { + display: flex; + overflow: hidden; + margin: auto; + width: 100%; + overflow: ellipses; + + .editableView-container-editing-oneLine, + .editableView-container-editing { + color: grey; + padding: 10px; + width: 100%; + } + + .editableView-input:hover, + .editableView-container-editing:hover, + .editableView-container-editing-oneLine:hover { + cursor: text + } + + .editableView-input { + outline-color: black; + letter-spacing: 2px; + color: grey; + border: 0px; + padding: 12px 10px 11px 10px; + } + + font-size: 75%; + letter-spacing: 2px; + cursor: pointer; + + .editableView-input { + outline-color: black; + letter-spacing: 2px; + color: grey; + border: 0px; + padding: 12px 10px 11px 10px; + } +} + +.collectionNoteTakingView { + display: flex; +} + +// 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; + } +} + +.collectionNoteTakingView { + height: 100%; + width: 100%; + position: absolute; + top: 0; + overflow-y: auto; + overflow-x: hidden; + flex-wrap: wrap; + transition: top .5s; + + >div { + position: relative; + display: block; + } + + .collectionNoteTakingViewFieldColumn { + height: max-content; + } + + .collectionNoteTakingViewFieldColumnDragging { + height: 100%; + } + + .collectionSchemaView-previewDoc { + height: 100%; + position: absolute; + } + + .collectionNoteTakingView-docView-container { + width: 45%; + margin: 5% 2.5%; + padding-left: 2.5%; + height: auto; + } + + .collectionNoteTakingView-Nodes { + width: 100%; + height: 100%; + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + top: 0; + left: 0; + width: 100%; + position: absolute; + } + + .collectionNoteTakingView-description { + font-size: 100%; + margin-bottom: 1vw; + padding: 10px; + height: 2vw; + width: 100%; + font-family: $sans-serif; + background: $dark-gray; + color: $white; + } + + .collectionNoteTakingView-columnDragger { + width: 15; + height: 15; + position: absolute; + margin-left: -5; + } + + // Documents in NoteTaking view + .collectionNoteTakingView-columnDoc { + display: flex; + // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change + position: relative; + + &:hover { + .hoverButtons{ + opacity: 1; + } + } + + .hoverButtons { + display: flex; + opacity: 0; + position: absolute; + height: 100%; + left: -35px; + justify-content: center; + align-items: center; + padding: 0px 10px; + + .buttonWrapper { + padding: 3px; + border-radius: 3px; + + &:hover { + background: rgba(0, 0, 0, 0.26); + } + } + } + } + + .collectionNoteTakingView-collapseBar { + margin-left: 2px; + margin-right: 2px; + margin-top: 2px; + background: $medium-gray; + height: 5px; + + &.active { + margin-left: 0; + margin-right: 0; + background: red; + } + } + + .collectionNoteTakingView-miniHeader { + width: 100%; + + .editableView-container-editing-oneLine { + min-height: 20px; + display: flex; + align-items: center; + flex-direction: row; + } + + span::before, + span::after { + content: ""; + width: 50%; + border-top: dashed gray 1px; + position: relative; + display: inline-block; + } + + span::before { + margin-right: 10px; + } + + span::after { + margin-left: 10px; + } + + span { + position: relative; + text-align: center; + white-space: nowrap; + overflow: visible; + width: 100%; + display: flex; + color: gray; + align-items: center; + } + } + + .collectionNoteTakingView-sectionHeader { + text-align: center; + margin: auto; + margin-bottom: 10px; + background: $medium-gray; + // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong + + .editableView-input { + color: $dark-gray; + } + + .editableView-input:hover, + .editableView-container-editing:hover, + .editableView-container-editing-oneLine:hover { + cursor: text + } + + .collectionNoteTakingView-sectionHeader-subCont { + outline: none; + border: 0px; + width: 100%; + letter-spacing: 2px; + font-size: 75%; + transition: transform 0.2s; + position: relative; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + color: $dark-gray; + + .editableView-container-editing-oneLine, + .editableView-container-editing { + color: grey; + padding: 10px; + } + + .editableView-input:hover, + .editableView-container-editing:hover, + .editableView-container-editing-oneLine:hover { + cursor: text; + } + + .editableView-input { + padding: 12px 10px 11px 10px; + border: 0px; + color: grey; + text-align: center; + letter-spacing: 2px; + outline-color: black; + } + } + + .collectionNoteTakingView-sectionColor { + position: absolute; + left: 0; + top: 0; + height: 100%; + display: none; + + [class*="css"] { + max-width: 102px; + } + + .collectionNoteTakingView-sectionColorButton { + height: 30px; + display: inherit; + } + + .collectionNoteTakingView-colorPicker { + width: 78px; + z-index: 10; + position: relative; + background: white; + + .colorOptions { + display: flex; + flex-wrap: wrap; + } + + .colorPicker { + cursor: pointer; + width: 20px; + height: 20px; + border-radius: 10px; + margin: 3px; + + &.active { + border: 2px solid white; + box-shadow: 0 0 0 2px lightgray; + } + } + } + } + + .collectionNoteTakingView-sectionOptions { + position: absolute; + right: 0; + top: 0; + height: 100%; + display: none; + + [class*="css"] { + max-width: 102px; + } + + .collectionNoteTakingView-sectionOptionButton { + height: 30px; + } + + .collectionNoteTakingView-optionPicker { + + .optionOptions { + display: inline; + } + + .optionPicker { + cursor: pointer; + height: 20px; + border-radius: 10px; + margin: 3px; + width: max-content; + + &.active { + color: red; + } + } + } + } + + .collectionNoteTakingView-sectionDelete { + position: absolute; + right: 25px; + top: 0; + height: 100%; + display: none; + } + } + + .collectionNoteTakingView-sectionHeader:hover { + .collectionNoteTakingView-sectionColor { + display: unset; + } + + .collectionNoteTakingView-sectionOptions { + display: unset; + } + + .collectionNoteTakingView-sectionDelete { + display: unset; + } + } + + .collectionNoteTakingView-addDocumentButton, + .collectionNoteTakingView-addGroupButton { + display: flex; + overflow: hidden; + margin: auto; + width: 90%; + overflow: ellipses; + + .editableView-container-editing-oneLine, + .editableView-container-editing { + color: grey; + padding: 10px; + width: 100%; + } + + .editableView-input:hover, + .editableView-container-editing:hover, + .editableView-container-editing-oneLine:hover { + cursor: text + } + + .editableView-input { + outline-color: black; + letter-spacing: 2px; + color: grey; + border: 0px; + padding: 12px 10px 11px 10px; + } + } + + .collectionNoteTakingView-addDocumentButton { + font-size: 75%; + letter-spacing: 2px; + cursor: pointer; + + .editableView-input { + outline-color: black; + letter-spacing: 2px; + color: grey; + border: 0px; + padding: 12px 10px 11px 10px; + } + } + + .collectionNoteTakingView-addGroupButton { + background: rgb(238, 238, 238); + font-size: 75%; + text-align: center; + letter-spacing: 2px; + height: fit-content; + } + + .rc-switch { + position: absolute; + display: inline-block; + bottom: 4px; + right: 4px; + width: 70px; + height: 30px; + border-radius: 40px 40px; + background-color: lightslategrey; + } + + .rc-switch:after { + position: absolute; + width: 22px; + height: 22px; + left: 3px; + top: 4px; + border-radius: 50% 50%; + background-color: #fff; + content: " "; + cursor: pointer; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.26); + -webkit-transform: scale(1); + transform: scale(1); + transition: left 0.3s cubic-bezier(0.35, 0, 0.25, 1); + -webkit-animation-timing-function: cubic-bezier(0.35, 0, 0.25, 1); + animation-timing-function: cubic-bezier(0.35, 0, 0.25, 1); + -webkit-animation-duration: 0.3s; + animation-duration: 0.3s; + } + + .rc-switch-checked:after { + left: 44px; + } + + .rc-switch-inner { + color: #fff; + font-size: 12px; + position: absolute; + left: 28px; + top: 8px; + } + + .rc-switch-checked .rc-switch-inner { + left: 8px; + } +} + +@media only screen and (max-device-width: 480px) { + + .collectionNoteTakingView .collectionNoteTakingView-columnDragger, + .collectionNoteTakingView-columnDragger { + width: 0.1; + height: 0.1; + opacity: 0; + font-size: 0; + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx new file mode 100644 index 000000000..9519e9aaa --- /dev/null +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -0,0 +1,702 @@ +import React = require("react"); +import { CursorProperty } from "csstype"; +import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; +import { observer } from "mobx-react"; +import { DataSym, Doc, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Copy, Id, ToScriptString, ToString } from "../../../fields/FieldSymbols"; +import { List } from "../../../fields/List"; +import { listSpec } from "../../../fields/Schema"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, smoothScroll, Utils } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DragManager, dropActionType } from "../../util/DragManager"; +import { SnappingManager } from "../../util/SnappingManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { LightboxView } from "../LightboxView"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from "../nodes/DocumentView"; +import { StyleProp } from "../StyleProvider"; +import "./CollectionNoteTakingView.scss"; +import CollectionNoteTakingViewDivider from "./CollectionNoteTakingViewDivider"; +import { CollectionNoteTakingViewColumn } from "./CollectionNoteTakingViewColumn"; +import { CollectionSubView } from "./CollectionSubView"; +import { CollectionViewType } from "./CollectionView"; +import { ObjectField } from "../../../fields/ObjectField"; +import { faThumbsDown } from "@fortawesome/free-solid-svg-icons"; +const _global = (window /* browser */ || global /* node */) as any; + +export type collectionNoteTakingViewProps = { + chromeHidden?: boolean; + viewType?: CollectionViewType; + NativeWidth?: () => number; + NativeHeight?: () => number; +}; + +//TODO: somehow need to update the mapping and then have everything else rerender. Maybe with a refresh boolean like +// in Hypermedia? + +@observer +export class CollectionNoteTakingView extends CollectionSubView<Partial<collectionNoteTakingViewProps>>() { + _pivotFieldDisposer?: IReactionDisposer; + _autoHeightDisposer?: IReactionDisposer; + _masonryGridRef: HTMLDivElement | null = null; + _draggerRef = React.createRef<HTMLDivElement>(); + // _docXfs: { height: () => number, width: () => number, noteTakingDocTransform: () => Transform }[] = []; + // @observable _docsByColumnHeader = new Map<string, Doc[]>(); + //TODO: need to make sure that we save the mapping + @observable docsDraggedRowCol: number[] = []; + @observable _cursor: CursorProperty = "grab"; + @observable _scroll = 0; // used to force the document decoration to update when scrolling + @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } + @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } + @computed get pivotField() { return "Col" } + @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } + @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } + @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } + @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } + @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } + @computed get numGroupColumns() { return this.columnHeaders.length; } + @observable columnStartXCoords: number[] = [] + @computed get PanelWidth() {return this.props.PanelWidth()} + @computed get maxColWdith() {return this.props.PanelWidth() - 2 * this.xMargin;} + + // If the user has not yet created any docs (in another view), this will create a single column. Otherwise, + // it will adjust according to the + constructor(props: any) { + super(props); + if (this.columnHeaders === undefined) { + this.layoutDoc._columnHeaders = new List<SchemaHeaderField>([new SchemaHeaderField('New Column')]); + this.columnStartXCoords = [0] + // add all of the docs that have not been added to a column to this new column + } else { + const numHeaders = this.columnHeaders.length + this.resizeColumns(numHeaders) + } + } + + // passed as a prop to the NoteTakingField, which uses this function + // to render the docs you see within an individual column. + children = (docs: Doc[]) => { + TraceMobx(); + return docs.map((d, i) => { + const height = () => this.getDocHeight(d); + const width = () => this.getDocWidth(d); + const style = { width: width(), marginTop: this.gridGap, height: height() }; + return <div className={`collectionNoteTakingView-columnDoc`} key={d[Id]} style={style} > + {this.getDisplayDoc(d, width)} + </div>; + }); + } + + // [CAVEATS] (1) keep track of the offsetting + // (2) documentView gets unmounted as you remove it from the list + get Sections() { + const columnHeaders = this.columnHeaders; + const sections = new Map<SchemaHeaderField, Doc[]>(columnHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); + let docs = this.childDocs + const rowCol = this.docsDraggedRowCol + + // filter out the currently dragged docs from the child docs, since we will insert them later + if (rowCol.length && DragManager.docsBeingDragged.length) { + const docIdsToRemove = new Set() + DragManager.docsBeingDragged.forEach(d => { + docIdsToRemove.add(d[Id]) + }) + docs = docs.filter(d => !docIdsToRemove.has(d[Id])) + } + + // this will sort the docs into the correct columns (minus the ones you're currently dragging) + docs.map(d => { + if (!d[this.pivotField]) { + d[this.pivotField] = columnHeaders.length > 0 ? columnHeaders[0].heading : `New Column` + }; + const sectionValue = d[this.pivotField] as object; + + // look for if header exists already + const existingHeader = columnHeaders.find(sh => sh.heading === sectionValue.toString()); + if (existingHeader) { + sections.get(existingHeader)!.push(d); + } + }); + + // now we add back in the docs that we're dragging + if (rowCol.length && DragManager.docsBeingDragged.length) { + const colHeader = columnHeaders[rowCol[1]] + // TODO: get the actual offset that occurs if the docs were in that column + const offset = 0 + sections.get(colHeader)?.splice(rowCol[0] - offset, 0, ...DragManager.docsBeingDragged) + } + return sections; + } + + componentDidMount() { + super.componentDidMount?.(); + // reset section headers when a new filter is inputted + this._pivotFieldDisposer = reaction( + () => this.pivotField, + () => this.layoutDoc._columnHeaders = new List() + ); + + this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, + autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + this.headerMargin + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))))); + } + + componentWillUnmount() { + super.componentWillUnmount(); + this._pivotFieldDisposer?.(); + this._autoHeightDisposer?.(); + } + + @action + moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { + return this.props.removeDocument?.(doc) && addDocument?.(doc) ? true : false; + } + + createRef = (ele: HTMLDivElement | null) => { + this._masonryGridRef = ele; + this.createDashEventsTarget(ele!); //so the whole grid is the drop target? + } + + @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); } + @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } + + addDocTab = (doc: Doc, where: string) => { + if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); + return true; + } + return this.props.addDocTab(doc, where); + } + + scrollToBottom = () => { + smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); + } + + // let's dive in and get the actual document we want to drag/move around + focusDocument = (doc: Doc, options?: DocFocusOptions) => { + Doc.BrushDoc(doc); + + let focusSpeed = 0; + const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]); + if (found) { + const top = found.getBoundingClientRect().top; + const localTop = this.props.ScreenToLocalTransform().transformPoint(0, top); + if (Math.floor(localTop[1]) !== 0) { + smoothScroll(focusSpeed = doc.presTransition || doc.presTransition === 0 ? NumCast(doc.presTransition) : 500, this._mainCont!, localTop[1] + this._mainCont!.scrollTop); + } + } + const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : ViewAdjustment.doNothing; + this.props.focus(this.rootDoc, { + willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => + new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) + }); + } + + styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { + if (property === StyleProp.Opacity && doc) { + if (this.props.childOpacity) { + return this.props.childOpacity(); + } + if (this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getValues(doc, NumCast(this.Document._currentFrame))?.opacity; + } + } + return this.props.styleProvider?.(doc, props, property); + } + + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + + // rules for displaying the documents + getDisplayDoc(doc: Doc, width: () => number) { + const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; + const height = () => this.getDocHeight(doc); + let dref: Opt<DocumentView>; + const noteTakingDocTransform = () => this.getDocTransform(doc, dref); + return <DocumentView ref={r => dref = r || undefined} + Document={doc} + DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} + renderDepth={this.props.renderDepth + 1} + PanelWidth={width} + PanelHeight={height} + styleProvider={this.styleProvider} + layerProvider={this.props.layerProvider} + docViewPath={this.props.docViewPath} + fitWidth={this.props.childFitWidth} + isContentActive={emptyFunction} + originalBackgroundColor={StrCast(doc.backgroundColor)} + //TODO: change this from a prop to a parameter passed into a function + isNoteTakingView={true} + isDocumentActive={this.isContentActive} + LayoutTemplate={this.props.childLayoutTemplate} + LayoutTemplateString={this.props.childLayoutString} + freezeDimensions={this.props.childFreezeDimensions} + NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined} // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox + NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.(doc) || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined} + 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} + ScreenToLocalTransform={noteTakingDocTransform} + focus={this.focusDocument} + docFilters={this.childDocFilters} + hideDecorationTitle={this.props.childHideDecorationTitle?.()} + hideResizeHandles={this.props.childHideResizeHandles?.()} + hideTitle={this.props.childHideTitle?.()} + docRangeFilters={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + ContainingCollectionDoc={this.props.CollectionView?.props.Document} + ContainingCollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.addDocTab} + bringToFront={returnFalse} + scriptContext={this.props.scriptContext} + pinToPres={this.props.pinToPres} + />; + } + + // This is used to get the coordinates of a document when we go from a view like freeform to columns + getDocTransform(doc: Doc, dref?: DocumentView) { + const y = this._scroll; // required for document decorations to update when the text box container is scrolled + const { scale, translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv || undefined); + // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform. so we have to subtract that off + return new Transform(- translateX + (dref?.centeringX || 0), - translateY + (dref?.centeringY || 0), 1).scale(this.props.ScreenToLocalTransform().Scale); + } + + // how to get the width of a document. Currently returns the width of the column (minus margins) + // if a note doc. Otherwise, returns the normal width (for graphs, images, etc...) + getDocWidth(d: Doc) { + const heading = d[this.pivotField] as object + const castedSectionValue = heading.toString() + const existingHeader = this.columnHeaders.find(sh => sh.heading === (castedSectionValue)); + const colStartXCoords = this.columnStartXCoords + if (!existingHeader) { + return 1000 + } + const index = this.columnHeaders.indexOf(existingHeader) + const endColValue = index == this.columnHeaders.length - 1 ? this.PanelWidth : this.columnStartXCoords[index+1] + const maxWidth = endColValue - colStartXCoords[index] - 3 * this.xMargin + if (d.type === DocumentType.RTF) { + return maxWidth + } + const width = d[WidthSym]() + return width < maxWidth ? width : maxWidth + } + + // how to get the height of a document. Nothing special here. + getDocHeight(d?: Doc) { + if (!d || d.hidden) return 0; + const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); + const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc; + const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1)); + const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[WidthSym]() : 0); + const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? d[HeightSym]() : 0); + if (nw && nh) { + // const colWid = this.columnWidth / this.numGroupColumns; + // const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid); + const docWid = this.getDocWidth(d) + return Math.min( + maxHeight, + docWid * nh / nw); + } + const childHeight = NumCast(childLayoutDoc._height); + const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.(d)) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin; + return Math.min(childHeight, maxHeight, panelHeight); + } + + // called when a column is either added or deleted. This function creates n evenly spaced columns + resizeColumns = (n: number) => { + const totalWidth = this.PanelWidth + const dividerWidth = 32 + const totaldividerWidth = (n - 1) * dividerWidth + const colWidth = (totalWidth - totaldividerWidth) / n + const newColXCoords: number[] = [] + let colStart = 0 + for (let i = 0; i < n; i++) { + newColXCoords.push(colStart) + colStart += colWidth + dividerWidth + } + this.columnStartXCoords = newColXCoords + } + + // This function is used to preview where a document will drop in a column once a drag is complete. + @action + onPointerOver = (e: React.PointerEvent) => { + if (DragManager.docsBeingDragged.length && this.childDocList) { + // get the current docs for the column based on the mouse's x coordinate + // will use again later, which is why we're saving as local + const xCoord = e.clientX - 2 * this.gridGap + const colDocs = this.getDocsFromXCoord(xCoord) + // get the index for where you need to insert the doc you are currently dragging + const clientY = e.clientY + let dropInd = -1; + // unsure whether we still want this dropAfter field + // let dropAfter = 0; + // manually set to 140, because not sure how to get exact value + let pos0 = 140 + colDocs.forEach((doc, i) => { + const noteTakingDocTransform = () => this.getDocTransform(doc); + let pos1 = noteTakingDocTransform().inverse().transformPoint(0, this.getDocHeight(doc) + 2 * this.gridGap)[1]; + pos1 += pos0 + // updating drop position based on y coordinates + const yCoordInBetween = clientY > pos0 && (clientY < pos1 || i == colDocs.length - 1) + if (yCoordInBetween) { + dropInd = i; + // dropAfter = 0; + if (clientY > (pos0 + pos1) / 2) { + // dropAfter = 1; + } + } + pos0 = pos1 + }) + // we alter the pivot fields of the docs in case they are moved to a new column. + const colIndex = this.getColumnFromXCoord(xCoord) + const colHeader = StrCast(this.columnHeaders[colIndex].heading) + DragManager.docsBeingDragged.forEach(d => d[this.pivotField] = colHeader) + // used to notify sections to re-render + // console.log([dropInd, this.getColumnFromXCoord(xCoord)]) + this.docsDraggedRowCol = [dropInd, this.getColumnFromXCoord(xCoord)] + } + } + + // returns the column index for a given x-coordinate + getColumnFromXCoord = (xCoord: number): number => { + const numColumns = this.columnHeaders.length + const coords = this.columnStartXCoords.slice() + coords.push(this.PanelWidth) + let colIndex = 0 + for (let i = 0; i < numColumns; i++) { + if (xCoord > coords[i] && xCoord < coords[i + 1]) { + colIndex = i + break + } + } + return colIndex + } + + // returns the docs of a column based on the x-coordinate provided. + getDocsFromXCoord = (xCoord: number): Doc[] => { + const colIndex = this.getColumnFromXCoord(xCoord) + const colHeader = StrCast(this.columnHeaders[colIndex].heading) + // const docs = this.childDocList + const docs = this.childDocs + const docsMatchingHeader: Doc[] = [] + if (docs) { + docs.map(d => { + if (d instanceof Promise) return; + const sectionValue = d[this.pivotField] as object; + if (sectionValue.toString() == colHeader) { + docsMatchingHeader.push(d) + } + }) + } + return docsMatchingHeader; + } + + @undoBatch + @action + onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + if (de.complete.docDragData) { + if (super.onInternalDrop(e, de)) { + DragManager.docsBeingDragged = [] + // this.docsDraggedRowCol = [] + // filter out the currently dragged docs from the child docs, since we will insert them later + const rowCol = this.docsDraggedRowCol + const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= this.childDocs.length); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note). + const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; + + // const docs = this.childDocs + const docs = this.childDocList + if (docs && newDocs.length) { + // remove the dragged documents from the childDocList + newDocs.filter(d => docs.indexOf(d) !== -1).forEach(d => docs.splice(docs.indexOf(d), 1)) + // if the doc starts a columnm (or the drop index is undefined), we can just push it to the front. Otherwise we need to add it to the column properly + //TODO: you need to update childDocList instead. It seems that childDocs is a copy of the actual array we want to modify + if (rowCol[0] <= 0) { + docs.splice(0, 0, ...newDocs) + } else { + const colDocs = this.getDocsFromXCoord(de.x) + const previousDoc = colDocs[rowCol[0] - 1] + const previousDocIndex = docs.indexOf(previousDoc) + console.log(`docs: ${previousDocIndex}`) + docs.splice(previousDocIndex + 1, 0, ...newDocs) + } + } + } + } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging + else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { + const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" }); + this.props.addDocument?.(source); + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: de.complete.linkDragData.linkSourceGetAnchor() }, "doc annotation", ""); // TODODO this is where in text links get passed + e.stopPropagation(); + } + else if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData); + return false; + } + + @undoBatch + internalAnchorAnnoDrop(e: Event, annoDragData: DragManager.AnchorAnnoDragData) { + const dropCreator = annoDragData.dropDocCreator; + annoDragData.dropDocCreator = (annotationOn: Doc | undefined) => { + const dropDoc = dropCreator(annotationOn); + return dropDoc || this.rootDoc; + }; + return true; + } + + // when dropping outside of the current noteTaking context (like a new tab, freeform view, etc...) + @undoBatch + @action + onExternalDrop = async (e: React.DragEvent): Promise<void> => { + const where = [e.clientX, e.clientY]; + let targInd = -1; + const docs = this.getDocsFromXCoord(where[0]) + docs.map((d, i) => { + const pos0 = this.getDocTransform(d).inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + const pos1 = this.getDocTransform(d).inverse().transformPoint(this.getDocWidth(d), this.getDocHeight(d)); + // const pos0 = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height()); + if (where[0] > pos0[0] && where[0] < pos1[0] && where[1] > pos0[1] && where[1] < pos1[1]) { + targInd = i; + } + }) + // this._docXfs.map((cd, i) => { + // const pos = cd.noteTakingDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); + // const pos1 = cd.noteTakingDocTransform().inverse().transformPoint(cd.width(), cd.height()); + // if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) { + // targInd = i; + // } + // }); + super.onExternalDrop(e, {}, () => { + if (targInd !== -1) { + const newDoc = this.childDocs[this.childDocs.length - 1]; + const docs = this.childDocList; + if (docs) { + docs.splice(docs.length - 1, 1); + docs.splice(targInd, 0, newDoc); + } + } + }); + } + + // setDocsForColHeader = (key: string, docs: Doc[]) => { + // this._docsByColumnHeader = new Map(this._docsByColumnHeader.set(key, docs)) + // } + + headings = () => Array.from(this.Sections); + + refList: any[] = []; + + sectionNoteTaking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { + const type = "number"; + return <CollectionNoteTakingViewColumn + unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} + observeHeight={ref => { + if (ref) { + this.refList.push(ref); + this.observer = new _global.ResizeObserver(action((entries: any) => { + if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { + const height = this.headerMargin + + Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); + if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { + this.props.setHeight(height); + } + } + })); + this.observer.observe(ref); + } + }} + addDocument={this.addDocument} + // docsByColumnHeader={this._docsByColumnHeader} + // setDocsForColHeader={this.setDocsForColHeader} + chromeHidden={this.chromeHidden} + columnHeaders={this.columnHeaders} + Document={this.props.Document} + DataDoc={this.props.DataDoc} + resizeColumns={this.resizeColumns.bind(this)} + renderChildren={this.children} + numGroupColumns={this.numGroupColumns} + gridGap={this.gridGap} + pivotField={this.pivotField} + columnStartXCoords={this.columnStartXCoords} + maxColWidth={this.maxColWdith} + PanelWidth={this.PanelWidth} + key={heading?.heading ?? ""} + headings={this.headings} + heading={heading?.heading ?? ""} + headingObject={heading} + docList={docList} + yMargin={this.yMargin} + type={type} + createDropTarget={this.createDashEventsTarget} + screenToLocalTransform={this.props.ScreenToLocalTransform} + editableViewProps={{ + GetValue: () => "", + SetValue: this.addGroup, + contents: "+ New Column" + }} + />; + } + + // called when adding a new columnHeader + @action + addGroup = (value: string) => { + if (value && this.columnHeaders) { + this.resizeColumns(this.columnHeaders.length + 1) + const schemaHdrField = new SchemaHeaderField(value); + this.columnHeaders.push(schemaHdrField); + DocUtils.addFieldEnumerations(undefined, this.pivotField, [{ title: value, _backgroundColor: "schemaHdrField.color" }]); + return true; + } + return false; + } + + sortFunc = (a: [SchemaHeaderField, Doc[]], b: [SchemaHeaderField, Doc[]]): 1 | -1 => { + const descending = StrCast(this.layoutDoc._columnsSort) === "descending"; + const firstEntry = descending ? b : a; + const secondEntry = descending ? a : b; + return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1; + } + + onContextMenu = (e: React.MouseEvent): void => { + // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout + if (!e.isPropagationStopped()) { + const subItems: ContextMenuProps[] = []; + subItems.push({ description: `${this.layoutDoc._columnsFill ? "Variable Size" : "Autosize"} Column`, event: () => this.layoutDoc._columnsFill = !this.layoutDoc._columnsFill, icon: "plus" }); + subItems.push({ description: `${this.layoutDoc._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); + subItems.push({ description: "Clear All", event: () => this.dataDoc.data = new List([]), icon: "times" }); + ContextMenu.Instance.addItem({ description: "Options...", subitems: subItems, icon: "eye" }); + } + } + + // used to reset column sizes when using the drag handlers + @action + setColumnStartXCoords = (movementX: number, colIndex: number) => { + const coords = [...this.columnStartXCoords] + coords[colIndex] += movementX + this.columnStartXCoords = coords + } + + @computed get renderedSections() { + TraceMobx(); + // let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]; + // if (this.pivotField) { + // const entries = Array.from(this.Sections.entries()); + // sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries; + // } + const entries = Array.from(this.Sections.entries()); + const sections = entries.sort(this.sortFunc) + const eles: JSX.Element[] = [] + for (let i = 0; i < sections.length; i++) { + const col = this.sectionNoteTaking(sections[i][0], sections[i][1]) + eles.push(col) + if (i < sections.length - 1) { + eles.push( + <CollectionNoteTakingViewDivider + index={i + 1} + setColumnStartXCoords={this.setColumnStartXCoords.bind(this)} + xMargin={this.xMargin} + /> + ) + } + } + return eles + } + + @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); + 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); } + + @computed get scaling() { return !this.nativeWidth ? 1 : this.props.PanelHeight() / this.nativeHeight; } + + @computed get backgroundEvents() { return SnappingManager.GetIsDragging(); } + observer: any; + render() { + TraceMobx(); + const buttonMenu = this.rootDoc.buttonMenu; + const noviceExplainer = this.rootDoc.explainer; + return ( + <> + {buttonMenu || noviceExplainer ? <div className="documentButtonMenu"> + {buttonMenu ? this.buttonMenu : null} + {Doc.UserDoc().noviceMode && noviceExplainer ? + <div className="documentExplanation"> + {noviceExplainer} + </div> + : null + } + </div> : null} + <div className="collectionNoteTakingView" + 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)} + onPointerOver={this.onPointerOver} + onDrop={this.onExternalDrop.bind(this)} + onContextMenu={this.onContextMenu} + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} + > + {this.renderedSections} + </div> + </> + + ); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx new file mode 100644 index 000000000..bdcb9c399 --- /dev/null +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -0,0 +1,330 @@ +import React = require("react"); +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { Id } from "../../../fields/FieldSymbols"; +import { RichTextField } from "../../../fields/RichTextField"; +import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { ImageField } from "../../../fields/URLField"; +import { TraceMobx } from "../../../fields/util"; +import { emptyFunction, returnEmptyString, setupMoveUpEvents } from "../../../Utils"; +import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { DragManager } from "../../util/DragManager"; +import { SnappingManager } from "../../util/SnappingManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { EditableView } from "../EditableView"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; +import "./CollectionNoteTakingView.scss"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; + +// So this is how we are storing a column +interface CSVFieldColumnProps { + Document: Doc; + DataDoc: Opt<Doc>; + docList: Doc[]; + heading: string; + pivotField: string; + chromeHidden?: boolean; + columnHeaders: SchemaHeaderField[] | undefined; + headingObject: SchemaHeaderField | undefined; + yMargin: number; + // columnWidth: number; + numGroupColumns: number; + gridGap: number; + type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; + headings: () => object[]; + renderChildren: (docs: Doc[]) => JSX.Element[]; + addDocument: (doc: Doc | Doc[]) => boolean; + createDropTarget: (ele: HTMLDivElement) => void; + screenToLocalTransform: () => Transform; + observeHeight: (myref: any) => void; + unobserveHeight: (myref: any) => void; + editableViewProps: any; + resizeColumns: (n: number) => void + columnStartXCoords: number[] + PanelWidth: number + maxColWidth: number + // docsByColumnHeader: Map<string, Doc[]> + // setDocsForColHeader: (key: string, docs: Doc[]) => void +} + +@observer +export class CollectionNoteTakingViewColumn extends React.Component<CSVFieldColumnProps> { + @observable private _background = "inherit"; + + @computed get columnWidth() { + // base cases + if (!this.props.columnHeaders || !this.props.headingObject || this.props.columnHeaders.length == 1) { + return this.props.maxColWidth + } + const i = this.props.columnHeaders.indexOf(this.props.headingObject) + if (i < 0) { + return this.props.maxColWidth + } + const endColValue = i == this.props.numGroupColumns - 1 ? this.props.PanelWidth : this.props.columnStartXCoords[i+1] + // TODO make the math work here. 35 is half of 70, which is the current width of the divider + return endColValue - this.props.columnStartXCoords[i] - 30 + } + + private dropDisposer?: DragManager.DragDropDisposer; + private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); + + @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; + @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + _ele: HTMLElement | null = null; + + // This is likely similar to what we will be doing. Why do we need to make these refs? + // is that the only way to have drop targets? + createColumnDropRef = (ele: HTMLDivElement | null) => { + this.dropDisposer?.(); + if (ele) { + this._ele = ele; + this.props.observeHeight(ele); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this)); + } + } + + componentWillUnmount() { + this.props.unobserveHeight(this._ele); + } + + @undoBatch + columnDrop = action((e: Event, de: DragManager.DropEvent) => { + const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; + drop.docs?.forEach(d => Doc.SetInPlace(d, this.props.pivotField, drop.val, false)); + }); + + getValue = (value: string): any => { + const parsed = parseInt(value); + if (!isNaN(parsed)) return parsed; + if (value.toLowerCase().indexOf("true") > -1) return true; + if (value.toLowerCase().indexOf("false") > -1) return false; + return value; + } + + @action + headingChanged = (value: string, shiftDown?: boolean) => { + const castedValue = this.getValue(value); + if (castedValue) { + if (this.props.columnHeaders?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { + return false; + } + this.props.docList.forEach(d => d[this.props.pivotField] = castedValue); + if (this.props.headingObject) { + this.props.headingObject.setHeading(castedValue.toString()); + this._heading = this.props.headingObject.heading; + } + return true; + } + return false; + } + + @action pointerEntered = () => SnappingManager.GetIsDragging() && (this._background = "#b4b4b4"); + @action pointerLeave = () => this._background = "inherit"; + textCallback = (char: string) => this.addNewTextDoc("-typed text-", false, true); + + @action + addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { + if (!value && !forceEmptyNote) return false; + const key = this.props.pivotField; + const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _fitWidth: true, title: value, _autoHeight: true }); + const colValue = this.getValue(this.props.heading); + newDoc[key] = colValue; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? "" : " "; + return this.props.addDocument?.(newDoc) || false; + } + + @action + deleteColumn = () => { + if (!this.props.columnHeaders) { + return + } + if (this.props.headingObject) { + const index = this.props.columnHeaders.indexOf(this.props.headingObject); + const newIndex = index == 0 ? 1 : index - 1 + const newHeader = this.props.columnHeaders[newIndex]; + this.props.docList.forEach(d => d[this.props.pivotField] = newHeader.heading.toString()) + this.props.columnHeaders.splice(index, 1); + this.props.resizeColumns(this.props.columnHeaders.length) + } + } + + headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); + + //TODO: I think this is where I'm supposed to edit stuff + startDrag = (e: PointerEvent, down: number[], delta: number[]) => { + console.log('in startDrag') + // is MakeAlias a way to make a copy of a doc without rendering it? + const alias = Doc.MakeAlias(this.props.Document); + // alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); + alias._width = this.columnWidth; + alias._pivotField = undefined; + let value = this.getValue(this._heading); + value = typeof value === "string" ? `"${value}"` : value; + alias.viewSpecScript = ScriptField.MakeFunction(`doc.${this.props.pivotField} === ${value}`, { doc: Doc.name }); + if (alias.viewSpecScript) { + const options = {hideSource: false} + DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY, options); + console.log('in startDrag') + return true; + } + return false; + } + + menuCallback = (x: number, y: number) => { + ContextMenu.Instance.clearItems(); + const layoutItems: ContextMenuProps[] = []; + const docItems: ContextMenuProps[] = []; + const dataDoc = this.props.DataDoc || this.props.Document; + const pivotValue = this.getValue(this.props.heading); + + DocUtils.addDocumentCreatorMenuItems((doc) => { + const key = this.props.pivotField; + doc[key] = this.getValue(this.props.heading); + FormattedTextBox.SelectOnLoad = doc[Id]; + return this.props.addDocument?.(doc); + }, this.props.addDocument, x, y, true, this.props.pivotField, pivotValue); + + Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof (dataDoc[fieldKey]) === "string").map(fieldKey => + docItems.push({ + description: ":" + fieldKey, event: () => { + const created = DocUtils.DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this.props.Document)); + if (created) { + if (this.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this.props.Document); + } + return this.props.addDocument?.(created); + } + }, icon: "compress-arrows-alt" + })); + Array.from(Object.keys(Doc.GetProto(dataDoc))).filter(fieldKey => DocListCast(dataDoc[fieldKey]).length).map(fieldKey => + docItems.push({ + description: ":" + fieldKey, event: () => { + const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); + if (created) { + const container = this.props.Document.resolvedDataDoc ? Doc.GetProto(this.props.Document) : this.props.Document; + if (container.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, container); + return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); + } + return this.props.addDocument?.(created) || false; + } + }, icon: "compress-arrows-alt" + })); + !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Doc Fields ...", subitems: docItems, icon: "eye" }); + !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: "Containers ...", subitems: layoutItems, icon: "eye" }); + ContextMenu.Instance.setDefaultItem("::", (name: string): void => { + Doc.GetProto(this.props.Document)[name] = ""; + const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); + if (created) { + if (this.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this.props.Document); + } + this.props.addDocument?.(created); + } + }); + ContextMenu.Instance.displayMenu(x, y, undefined, true); + } + + @computed get innards() { + TraceMobx(); + const key = this.props.pivotField; + const heading = this._heading; + const columnYMargin = this.props.headingObject ? 0 : this.props.yMargin; + const evContents = heading ? heading : "25"; + const headingView = this.props.headingObject ? + <div key={heading} className="collectionNoteTakingView-sectionHeader" ref={this._headerRef} + style={{ + marginTop: 2 * this.props.yMargin, + // width: (this.props.columnWidth) / + // ((uniqueHeadings.length) || 1) + width: this.columnWidth - 20 + }}> + <div className="collectionNoteTakingView-sectionHeader-subCont" onPointerDown={this.headerDown} + title={evContents === `No Value` ? + `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""} + style={{ background: evContents !== `No Value` ? this._color : "inherit" }}> + <EditableView + GetValue={() => evContents} + SetValue={this.headingChanged} + contents={evContents} + oneLine={true} + /> + </div> + </div> : (null); + // const templatecols = `${this.props.columnWidth / this.props.numGroupColumns}px `; + const templatecols = `${this.columnWidth}px `; + const type = this.props.Document.type; + return <> + {headingView} + {<div> + <div key={`${heading}-stack`} className={`collectionNoteTakingView-Nodes`} + style={{ + padding: `${columnYMargin}px ${0}px ${this.props.yMargin}px ${0}px`, + margin: "auto", + width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, + height: 'max-content', + position: "relative", + gridGap: this.props.gridGap, + gridTemplateColumns: templatecols, + gridAutoRows: "0px" + }}> + {this.props.renderChildren(this.props.docList)} + </div> + + {!this.props.chromeHidden && type !== DocumentType.PRES ? + <div className="collectionNoteTakingView-DocumentButtons" + // style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}> + style={{ width: this.columnWidth - 20, marginBottom: 10 }}> + <div key={`${heading}-add-document`} className="collectionNoteTakingView-addDocumentButton"> + <EditableView + GetValue={returnEmptyString} + SetValue={this.addNewTextDoc} + textCallback={this.textCallback} + placeholder={"Type ':' for commands"} + contents={"+ New Node"} + menuCallback={this.menuCallback} + /> + </div> + <div key={`${this.props.Document[Id]}-addGroup`} className="collectionNoteTakingView-addDocumentButton"> + <EditableView {...this.props.editableViewProps} /> + </div> + {(this.props.columnHeaders?.length && this.props.columnHeaders.length > 1) && + <button className="collectionNoteTakingView-sectionDelete" onClick={this.deleteColumn}> + <FontAwesomeIcon icon="trash" size="lg" /> + </button> + } + </div> + : null} + </div> + } + </>; + } + + + render() { + TraceMobx(); + const heading = this._heading; + return ( + <div className={"collectionNoteTakingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading} + style={{ + //TODO: change this so that it's based on the column width + width: this.columnWidth, + height: "100%", + background: this._background + }} + ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}> + {this.innards} + </div > + ); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx new file mode 100644 index 000000000..ed5dc3715 --- /dev/null +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -0,0 +1,61 @@ +import { action, observable } from "mobx"; +import * as React from "react"; + +interface DividerProps { + index: number + xMargin: number + setColumnStartXCoords: (movementX: number, colIndex: number) => void +} + +export default class Divider extends React.Component<DividerProps>{ + @observable private isHoverActive = false; + @observable private isResizingActive = false; + + @action + private registerResizing = (e: React.PointerEvent<HTMLDivElement>) => { + e.stopPropagation(); + e.preventDefault(); + window.removeEventListener("pointermove", this.onPointerMove); + window.removeEventListener("pointerup", this.onPointerUp); + window.addEventListener("pointermove", this.onPointerMove); + window.addEventListener("pointerup", this.onPointerUp); + this.isResizingActive = true; + } + + @action + private onPointerUp = () => { + this.isResizingActive = false; + this.isHoverActive = false; + window.removeEventListener("pointermove", this.onPointerMove); + window.removeEventListener("pointerup", this.onPointerUp); + } + + @action + onPointerMove = ({ movementX }: PointerEvent) => { + this.props.setColumnStartXCoords(movementX, this.props.index) + } + + render() { + return ( + <div className="columnResizer" + style={{ + display: "flex", + alignItems: "center", + cursor: "col-resize" + }} + onPointerEnter={action(() => this.isHoverActive = true)} + onPointerLeave={action(() => !this.isResizingActive && (this.isHoverActive = false))} + > + <div className="columnResizer-handler" onPointerDown={e => this.registerResizing(e)} + style={{ + height: "95%", + width: 12, + borderRight: "4px solid #282828", + borderLeft: "4px solid #282828", + margin: "0px 10px" + }} + /> + </div> + ) + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 73572299a..9a498858a 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -130,9 +130,37 @@ margin-left: -5; } + // Documents in stacking view .collectionStackingView-columnDoc { - display: inline-block; - margin: auto; + display: flex; + // margin: auto; // Removed auto so that it is no longer center aligned - this could be something we change + position: relative; + + &:hover { + .hoverButtons{ + opacity: 1; + } + } + + .hoverButtons { + display: flex; + opacity: 0; + position: absolute; + height: 100%; + left: -35px; + justify-content: center; + align-items: center; + padding: 0px 10px; + + .buttonWrapper { + padding: 3px; + border-radius: 3px; + + &:hover { + background: rgba(0, 0, 0, 0.26); + } + } + } } .collectionStackingView-masonryDoc { @@ -203,6 +231,7 @@ .collectionStackingView-sectionHeader { text-align: center; margin: auto; + margin-bottom: 10px; background: $medium-gray; // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong @@ -385,6 +414,7 @@ .collectionStackingView-addDocumentButton { font-size: 75%; letter-spacing: 2px; + cursor: pointer; .editableView-input { outline-color: black; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4e8c14039..2ec9e3fc5 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -35,6 +35,7 @@ const _global = (window /* browser */ || global /* node */) as any; export type collectionStackingViewProps = { chromeHidden?: boolean; + // view type is stacking viewType?: CollectionViewType; NativeWidth?: () => number; NativeHeight?: () => number; @@ -43,47 +44,73 @@ export type collectionStackingViewProps = { @observer export class CollectionStackingView extends CollectionSubView<Partial<collectionStackingViewProps>>() { _masonryGridRef: HTMLDivElement | null = null; + // used in a column dragger, likely due for the masonry grid view. We want to use this _draggerRef = React.createRef<HTMLDivElement>(); + // Not sure what a pivot field is. Seems like we cause reaction in MobX get rid of it once we exit this view _pivotFieldDisposer?: IReactionDisposer; + // Seems like we cause reaction in MobX get rid of our height once we exit this view _autoHeightDisposer?: IReactionDisposer; + // keeping track of documents. Updated on internal and external drops. What's the difference? _docXfs: { height: () => number, width: () => number, stackedDocTransform: () => Transform }[] = []; + // Doesn't look like this field is being used anywhere. Obsolete? _columnStart: number = 0; + // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map<string, number>(); + // Assuming that this is the current css cursor style @observable _cursor: CursorProperty = "grab"; + // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling + // does this mean whether the browser is hidden? Or is chrome something else entirely? @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } + // it looks like this gets the column headers that Mehek was showing just now @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); } + // Still not sure what a pivot is, but it appears that we can actually filter docs somehow? @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } + // filteredChildren is what you want to work with. It's the list of things that you're currently displaying @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => (pair.layout instanceof Doc) && !pair.layout.hidden).map(pair => pair.layout); } + // how much margin we give the header @computed get headerMargin() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin); } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } @computed get yMargin() { return this.props.yPadding || NumCast(this.layoutDoc._yMargin, 5); } // 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } - @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; } + // are we stacking or masonry? + @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === (CollectionViewType.Stacking || CollectionViewType.NoteTaking); } + // this is the number of StackingViewFieldColumns that we have @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } + // reveals a button to add a group in masonry view @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; } + // columnWidth handles the margin on the left and right side of the documents @computed get columnWidth() { return Math.min(this.props.PanelWidth() - 2 * this.xMargin, this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250)); } + @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } constructor(props: any) { super(props); if (this.columnHeaders === undefined) { + // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out? + // here we're making an empty list of column headers (again, what Mehek showed us) this.layoutDoc._columnHeaders = new List<SchemaHeaderField>(); } } + // TODO: plj - these are the children children = (docs: Doc[]) => { + //TODO: can somebody explain me to what exactly TraceMobX is? TraceMobx(); + // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; return docs.map((d, i) => { const height = () => this.getDocHeight(d); const width = () => this.getDocWidth(d); + // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); + // just getting the style const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; + // So we're choosing whether we're going to render a column or a masonry doc return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} style={style} > {this.getDisplayDoc(d, width)} </div>; @@ -94,7 +121,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this._heightMap.set(key, sectionHeight); } + // is sections that all collections inherit? I think this is how we show the masonry/columns + //TODO: this seems important get Sections() { + // appears that pivot field IS actually for sorting if (!this.pivotField || this.columnHeaders instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); if (this.columnHeaders === undefined) { @@ -123,6 +153,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } }); // remove all empty columns if hideHeadings is set + // we will want to have something like this, so that we can hide columns and add them back in if (this.layoutDoc._columnsHideIfEmpty) { Array.from(fields.keys()).filter(key => !fields.get(key)!.length).map(header => { fields.delete(header); @@ -142,6 +173,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection () => this.pivotField, () => this.layoutDoc._columnHeaders = new List() ); + //TODO: where the heck are we getting filters from? this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + (this.isStackingView ? @@ -179,6 +211,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection smoothScroll(500, this._mainCont!, this._mainCont!.scrollHeight); } + // let's dive in and get the actual document we want to drag/move around focusDocument = (doc: Doc, options?: DocFocusOptions) => { Doc.BrushDoc(doc); @@ -229,6 +262,9 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } } isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + + // this is what renders the document that you see on the screen + // called in Children: this actually adds a document to our children list isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; @@ -237,6 +273,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection let dref: Opt<DocumentView>; const stackedDocTransform = () => this.getDocTransform(doc, dref); this._docXfs.push({ stackedDocTransform, width, height }); + //DocumentView is how the node will be rendered return <DocumentView ref={r => dref = r || undefined} Document={doc} DataDoc={dataDoc || (!Doc.AreProtosEqual(doc[DataSym], doc) && doc[DataSym])} @@ -291,7 +328,13 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection getDocWidth(d?: Doc) { if (!d) return 0; const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.()); - const maxWidth = this.columnWidth / this.numGroupColumns; + // TODO: pj - replace with a better way to calculate the margin + let margin = 25; + d.margin = 25; + if (this.columnWidth < 150){ + margin = 0; + } + const maxWidth = (this.columnWidth / this.numGroupColumns) - (margin * 2); if (!this.layoutDoc._columnsFill && !(childLayoutDoc._fitWidth || this.props.childFitWidth?.(d))) { return Math.min(d[WidthSym](), maxWidth); } @@ -316,6 +359,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return Math.min(childHeight, maxHeight, panelHeight); } + // This following three functions must be from the view Mehek showed columnDividerDown = (e: React.PointerEvent) => { runInAction(() => this._cursor = "grabbing"); setupMoveUpEvents(this, e, this.onDividerMove, action(() => this._cursor = "grab"), emptyFunction); @@ -333,13 +377,46 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection </div>; } + // TODO: plj + @action + onPointerOver = (e: React.PointerEvent) => { + // console.log("hovering over something") + if (DragManager.docsBeingDragged.length) { + // essentially copying code from onInternalDrop for this: + const doc = DragManager.docsBeingDragged[0] + // console.log(doc[LayoutSym]()) + + console.log(doc[DataSym]) + console.log(Doc.IndexOf(doc, this.childDocs)) + + } + + + } + + //used in onPointerOver to swap two nodes in the rendered filtered children list + swapNodes = (i: number, j: number) => { + + } + + //plj added this + @action + onPointerDown = (e: React.PointerEvent) => { + + } + + // TODO: plj - look at this. Start with making changes to db, and then transition to client side @undoBatch @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { + // Fairly confident that this is where the swapping of nodes in the various arrays happens + console.log('drop') const where = [de.x, de.y]; + // start at -1 until we're sure we want to add it to the column let dropInd = -1; let dropAfter = 0; if (de.complete.docDragData) { + // going to re-add the docs to the _docXFs based on position of where we just dropped this._docXfs.map((cd, i) => { const pos = cd.stackedDocTransform().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.stackedDocTransform().inverse().transformPoint(cd.width(), cd.height()); @@ -351,11 +428,14 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }); const oldDocs = this.childDocs.length; if (super.onInternalDrop(e, de)) { + // check to see if we actually need anything to the new column of nodes (if droppedDocs != empty) const droppedDocs = this.childDocs.slice().filter((d: Doc, ind: number) => ind >= oldDocs); // if the drop operation adds something to the end of the list, then use that as the new document (may be different than what was dropped e.g., in the case of a button which is dropped but which creates say, a note). const newDocs = droppedDocs.length ? droppedDocs : de.complete.docDragData.droppedDocuments; // if nothing was added to the end of the list, then presumably the dropped documents were already in the list, but possibly got reordered so we use them. const docs = this.childDocList; + // reset drag manager docs, because we just dropped DragManager.docsBeingDragged = []; + // still figuring out where to add the document if (docs && newDocs.length) { const insertInd = dropInd === -1 ? docs.length : dropInd + dropAfter; const offset = newDocs.reduce((off, ndoc) => this.filteredChildren.find((fdoc, i) => ndoc === fdoc && i < insertInd) ? off + 1 : off, 0); @@ -363,7 +443,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection docs.splice(insertInd - offset, 0, ...newDocs); } } - } + } // it seems like we're creating a link here. Weird. I didn't know that you could establish links by dragging else if (de.complete.linkDragData?.dragDocument.context === this.props.Document && de.complete.linkDragData?.linkDragView?.props.CollectionFreeFormDocumentView?.()) { const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, _fitWidth: true, title: "dropped annotation" }); this.props.addDocument?.(source); @@ -386,7 +466,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @undoBatch @action + //What is the difference between internal and external drop?? Does internal mean we're dropping inside of a collection? + // I take it back: external drop means we took it out of column/collection that we were just in onExternalDrop = async (e: React.DragEvent): Promise<void> => { + console.log('external drop') const where = [e.clientX, e.clientY]; let targInd = -1; this._docXfs.map((cd, i) => { @@ -407,8 +490,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } }); } + // sections are important headings = () => Array.from(this.Sections); refList: any[] = []; + // what a section looks like if we're in stacking view sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; @@ -418,6 +503,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection type = types[0]; } } + //TODO: I think that we only have one of these atm return <CollectionStackingViewFieldColumn unobserveHeight={ref => this.refList.splice(this.refList.indexOf(ref), 1)} observeHeight={ref => { @@ -458,6 +544,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection />; } + // what a section looks like if we're in masonry. Shouldn't actually need to use this. sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[], first: boolean) => { const key = this.pivotField; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; @@ -500,6 +587,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } @action + // What are we adding a group to? addGroup = (value: string) => { if (value && this.columnHeaders) { const schemaHdrField = new SchemaHeaderField(value); @@ -527,6 +615,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } } + // @computed get renderedSections() { TraceMobx(); let sections = [[undefined, this.filteredChildren] as [SchemaHeaderField | undefined, Doc[]]]; @@ -534,6 +623,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const entries = Array.from(this.Sections.entries()); sections = this.layoutDoc._columnsSort ? entries.sort(this.sortFunc) : entries; } + // a section will have a header and a list of docs. Ok cool. return sections.map((section, i) => this.isStackingView ? this.sectionStacking(section[0], section[1]) : this.sectionMasonry(section[0], section[1], i === 0)); } @@ -590,6 +680,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const editableViewProps = { GetValue: () => "", SetValue: this.addGroup, + // I don't recall ever seeing this add a group button contents: "+ ADD A GROUP" }; const buttonMenu = this.rootDoc.buttonMenu; @@ -614,10 +705,16 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection pointerEvents: this.backgroundEvents ? "all" : undefined }} onScroll={action(e => this._scroll = e.currentTarget.scrollTop)} + onPointerOver={this.onPointerOver} + onPointerDown={this.onPointerDown} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} + // Todo: what is wheel? Are we talking about a mouse wheel? onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > + {/* so it appears that we are actually rendering the sections. Maybe this is what we're looking for? */} {this.renderedSections} + {/* I think that showAddGroup must be passed in as false, which is why we can't find what Mehek showed + Or it's because we aren't passing a pivot field */} {!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 }}> diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index f3a798143..b22c0e687 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -26,6 +26,7 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; +// So this is how we are storing a column interface CSVFieldColumnProps { Document: Doc; DataDoc: Opt<Doc>; @@ -41,6 +42,7 @@ interface CSVFieldColumnProps { gridGap: number; type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; headings: () => object[]; + // I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure renderChildren: (docs: Doc[]) => JSX.Element[]; addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; @@ -62,6 +64,8 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC _ele: HTMLElement | null = null; + // This is likely similar to what we will be doing. Why do we need to make these refs? + // is that the only way to have drop targets? createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) { @@ -75,6 +79,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC this.props.unobserveHeight(this._ele); } + //TODO: what is scripting? I found it in SetInPlace def but don't know what that is @undoBatch columnDrop = action((e: Event, de: DragManager.DropEvent) => { const drop = { docs: de.complete.docDragData?.droppedDocuments, val: this.getValue(this._heading) }; @@ -146,7 +151,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC headerDown = (e: React.PointerEvent<HTMLDivElement>) => setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); + //TODO: I think this is where I'm supposed to edit stuff startDrag = (e: PointerEvent, down: number[], delta: number[]) => { + // is MakeAlias a way to make a copy of a doc without rendering it? const alias = Doc.MakeAlias(this.props.Document); alias._width = this.props.columnWidth / (this.props.columnHeaders?.length || 1); alias._pivotField = undefined; @@ -313,13 +320,20 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC {this.props.renderChildren(this.props.docList)} </div> {!this.props.chromeHidden ? + // TODO: this is the "new" button: see what you can work with here + // change cursor to pointer for this, and update dragging cursor + //TODO: there is a bug that occurs when adding a freeform document and trying to move it around + //TODO: would be great if there was additional space beyond the frame, so that you can actually see your + // bottom note + //TODO: ok, so we are using a single column, and this is it! <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" - style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}> + style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10, marginLeft: 25 }}> <EditableView GetValue={returnEmptyString} SetValue={this.addNewTextDoc} textCallback={this.textCallback} - contents={"+ NEW"} + placeholder={"Type ':' for commands"} + contents={<FontAwesomeIcon icon={"plus"}/>} toggle={this.toggleVisibility} menuCallback={this.menuCallback} /> </div> diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index b432104a1..59e22a48d 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -26,6 +26,7 @@ import { CollectionGridView } from './collectionGrid/CollectionGridView'; import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; +import { CollectionNoteTakingView } from './CollectionNoteTakingView'; import { CollectionPileView } from './CollectionPileView'; import { CollectionSchemaView } from "./collectionSchema/CollectionSchemaView"; import { CollectionStackingView } from './CollectionStackingView'; @@ -36,6 +37,7 @@ import './CollectionView.scss'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); +//TODO: see everywhere that this exists export enum CollectionViewType { Invalid = "invalid", Freeform = "freeform", @@ -54,7 +56,8 @@ export enum CollectionViewType { Map = "map", Grid = "grid", Pile = "pileup", - StackedTimeline = "stacked timeline" + StackedTimeline = "stacked timeline", + NoteTaking = "notetaking" } export interface CollectionViewProps extends FieldViewProps { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) @@ -138,6 +141,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab case CollectionViewType.Carousel: return <CollectionCarouselView key="collview" {...props} />; case CollectionViewType.Carousel3D: return <CollectionCarousel3DView key="collview" {...props} />; case CollectionViewType.Stacking: return <CollectionStackingView key="collview" {...props} />; + case CollectionViewType.NoteTaking: return <CollectionNoteTakingView key="collview" {...props} />; case CollectionViewType.Masonry: return <CollectionStackingView key="collview" {...props} />; case CollectionViewType.Time: return <CollectionTimeView key="collview" {...props} />; case CollectionViewType.Grid: return <CollectionGridView key="collview" {...props} />; @@ -154,6 +158,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" }); subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" }); subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" }); + subItems.push({ description: "Note taking", event: () => func(CollectionViewType.NoteTaking)._autoHeight = true, icon: "ellipsis-v" }); subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" }); subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" }); subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" }); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 704b8989a..ae7538160 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -246,6 +246,7 @@ export class TreeView extends React.Component<TreeViewProps> { document.removeEventListener("pointerup", this.onDragUp, true); document.removeEventListener("pointermove", this.onDragMove, true); } + // TODO: Parker look at this onDragMove = (e: PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); const pt = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ea976813..b80d4579a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -155,6 +155,9 @@ export interface DocumentViewSharedProps { scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document createNewFilterDoc?: () => void; updateFilterDoc?: (doc: Doc) => void; + // Parker added both of these + originalBackgroundColor?: string; + isNoteTakingView?: boolean; } // these props are specific to DocuentViews @@ -429,7 +432,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps startDragging(x: number, y: number, dropAction: dropActionType, hideSource = false) { if (this._mainCont.current) { - const dragData = new DragManager.DocumentDragData([this.props.Document]); + const dragData = new DragManager.DocumentDragData([this.props.Document]); //put a set of + if (this.props.isNoteTakingView) { + dragData.draggedDocuments.forEach((doc) => { + doc.backgroundColor = "#C9DAEF" + doc.opacity = 0.5 + }) + } const [left, top] = this.props.ScreenToLocalTransform().scale(this.ContentScale).inverse().transformPoint(0, 0); dragData.offset = this.props.ScreenToLocalTransform().scale(this.ContentScale).transformDirection(x - left, y - top); dragData.offset[0] = Math.min(this.rootDoc[WidthSym](), dragData.offset[0]); @@ -438,10 +447,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps dragData.treeViewDoc = this.props.treeViewDoc; dragData.removeDocument = this.props.removeDocument; dragData.moveDocument = this.props.moveDocument; + //dragData.dimSource : + // dragEffects field, set dim + // add kv pairs to a doc, swap properties with the node while dragging, and then swap when dropping + // add a dragEffects prop to DocumentView as a function that sets up. Each view has its own prop, when you start dragging: + // in Draganager, figure out which doc(s) you're dragging and change what opacity function returns const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView())); - DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStart) }, - () => setTimeout(action(() => ffview && (ffview.ChildDrag = undefined)))); // this needs to happen after the drop event is processed. + ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView())); + DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: hideSource || (!dropAction && !this.layoutDoc.onDragStar &&!this.props.isNoteTakingView)}, + () => setTimeout(action(() => { + ffview && (ffview.ChildDrag = undefined) + //TODO: is there a better way than adding another field to the props? Not quite sure how "this" works tbh + if (this.props.isNoteTakingView) { + this.props.Document.backgroundColor = ""; + this.props.Document.opacity = 1; + } + }))); // this needs to happen after the drop event is processed. ffview?.setupDragLines(false); } } diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index bdf0f9d64..7e4d23912 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -7,6 +7,7 @@ import { Id } from '../../../../fields/FieldSymbols'; import { emptyFunction, OmitKeys, returnAll, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; +import { CollectionNoteTakingView } from '../../collections/CollectionNoteTakingView'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; import { CollectionViewType } from '../../collections/CollectionView'; import { ViewBoxAnnotatableProps } from '../../DocComponent'; @@ -44,7 +45,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi } - _stack: CollectionStackingView | null | undefined; + _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined; childFitWidth = (doc:Doc) => doc.type === DocumentType.RTF; addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, "data", d), true as boolean); removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, "data", d), true as boolean); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 9f858539f..b169ad6cd 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -124,6 +124,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); } @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } @computed get scrollable(): boolean { + //TODO: likely do NOT have to update this for note-taking view, but still worth putting here if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; else return false; } diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 1a2f4b93f..6f853b2bc 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -185,6 +185,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { /** * Function to drag and drop the pres element to a diferent location */ + //TODO: this is what you need to look into startDrag = (e: PointerEvent) => { const miniView: boolean = this.toolbarWidth <= 100; const activeItem = this.rootDoc; |