From 9d48c95e5a556f5be4abde83d9443e384a33197c Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 10 Jul 2023 14:26:37 -0400 Subject: Location metadata synced and reactions working --- src/fields/Doc.ts | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/fields') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index deda4c876..0cf4256d6 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -522,6 +522,13 @@ export namespace Doc { export function IsDelegateField(doc: Doc, fieldKey: string) { return doc && Get(doc, fieldKey, true) !== undefined; } + // + // this will write the value to the key on either the data doc or the embedding doc. The choice + // of where to write it is based on: + // 1) if the embedding Doc already has this field defined on it, then it will be written to the embedding + // 2) if the data doc has the field, then it's written there. + // 3) if neither already has the field, then 'defaultProto' determines whether to write it to the data doc (or the embedding) + // export async function SetInPlace(doc: Doc, key: string, value: Field | undefined, defaultProto: boolean) { if (key.startsWith('_')) key = key.substring(1); const hasProto = Doc.GetProto(doc) !== doc ? Doc.GetProto(doc) : undefined; -- cgit v1.2.3-70-g09d2 From b3c0db8e2d6f29b6b09474b8252760e95f80de95 Mon Sep 17 00:00:00 2001 From: eperelm2 Date: Mon, 24 Jul 2023 14:46:05 -0400 Subject: filter- fixed removable and css (collapse bugs) --- src/client/views/FilterPanel.scss | 6 ++ src/client/views/FilterPanel.tsx | 112 +++++++++++++++++++------------------- src/fields/Doc.ts | 1 + 3 files changed, 64 insertions(+), 55 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index bb68afed3..bb1e79d11 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -209,6 +209,12 @@ font-size: 16; } + .filterBox-facetHeader-remove{ + // margin-left: auto; + float: right; + font-size: 16; + } + } diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 4f4e39218..c99967ef7 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -14,6 +14,8 @@ import { undoable } from '../util/UndoManager'; import { AiOutlineMinusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; +//slight bug when you don't click on background canvas before creating filter and the you click on the canvas + interface filterProps { rootDoc: Doc; } @@ -103,6 +105,8 @@ export class FilterPanel extends React.Component { return { strings: Array.from(valueSet.keys()), rtFields }; } + + public removeFilter = (filterName: string) => { Doc.setDocFilter(this.targetDoc, filterName, undefined, 'remove'); Doc.setDocRangeFilter(this.targetDoc, filterName, undefined); @@ -110,11 +114,10 @@ export class FilterPanel extends React.Component { @observable _chosenFacets = new ObservableMap(); @observable _chosenFacetsCollapse = new ObservableMap(); - @observable _removeBoolean = false; - // @observable _currentFilters: string[] ; -- instatitae array to store the currently selected filters (Ex: #food ). remove these filters when removing + @observable _currentActiveFilters = new ObservableMap(); @computed get activeFacets() { - console.log("chosen collpase " + this._chosenFacetsCollapse) + // console.log("chosen collpase " + this._chosenFacetsCollapse) const facets = new Map(this._chosenFacets); StrListCast(this.targetDoc?._childFilters).map(filter => facets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')); setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')))); @@ -151,6 +154,25 @@ export class FilterPanel extends React.Component { this._chosenFacetsCollapse.set(facetHeader, false) }; + @action + sortingCurrentFacetValues = (facetHeader:string) => { + + let returnKeys = []; + + console.log("this is current filtes " + this._currentActiveFilters) + for (var key of this.facetValues(facetHeader)){ + console.log("key : " + key ) + if (this._currentActiveFilters.get(key)){ + console.log("pushing") + returnKeys.push(key) + }} + console.log("this is return keys " + returnKeys) + return returnKeys.toString(); + } + + + + facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); @@ -175,7 +197,6 @@ export class FilterPanel extends React.Component { return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2)); }; - // change css and make the currently selected filters appear at the top render() { const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); @@ -205,41 +226,37 @@ export class FilterPanel extends React.Component {
{facetHeader.charAt(0).toUpperCase() + facetHeader.slice(1)} -
-
{ + {/*
*/} +
{ const collapseBoolValue = this._chosenFacetsCollapse.get(facetHeader) this._chosenFacetsCollapse.set(facetHeader, !collapseBoolValue )})}> -
+ + + +
-
{ - - // delete this.activeFacets[facetHeader]; - // StrListCast(this.targetDoc._childFilters).find(filter => filter.split(Doc.FilterSep)[2] = 'remove') - // .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) - // ?.split(Doc.FilterSep)[2] === 'check' - // console.log("why cant i get this "+ console.log( StrListCast(this.targetDoc._childFilters) - // .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader))) - this.activeFacets.delete(facetHeader) - this._chosenFacets.delete(facetHeader) - this._chosenFacetsCollapse.delete(facetHeader) - // this._removeBoolean = true - // setTimeout(() => {this._removeBoolean = false}, 1000 ) - // console.log("TRYING SOMETHING NEW " + e.target.checked) - - // check if the current values are apart of the this.facetValues(facetHeader).map(fval ... - - // SET UP BOOLEAN AND IF IT IS TRUE MEANS ITS CLICKED AND THEN ALL BOXES SHOULD BE UNCHECKED - } - ) - } - // onChange = {undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, 'check'), 'set filter')} - > -
-
- +
{ + for (var key of this.facetValues(facetHeader)){ + if (this._currentActiveFilters.get(key)){ + Doc.setDocFilter(this.targetDoc, facetHeader, key, 'remove') + this._currentActiveFilters.delete(facetHeader) + }} + + this.activeFacets.delete(facetHeader) + this._chosenFacets.delete(facetHeader) + this._chosenFacetsCollapse.delete(facetHeader) + + })} > + +
+ {/*
*/} + - { this._chosenFacetsCollapse.get(facetHeader) ? null : this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader) } + { this._chosenFacetsCollapse.get(facetHeader) ? + this.sortingCurrentFacetValues(facetHeader) : this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader) } {/* */} ))} @@ -263,7 +280,6 @@ export class FilterPanel extends React.Component { /> ); case 'checkbox': - // console.log("checking") return this.facetValues(facetHeader).map(fval => { const facetValue = fval; @@ -271,7 +287,7 @@ export class FilterPanel extends React.Component {
filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) ?.split(Doc.FilterSep)[2] === 'check' @@ -279,25 +295,11 @@ export class FilterPanel extends React.Component { } type={type} onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} - // onClick={undoable (e => - // e.target.checked ? this._currentFilters.push(fval) : , 'set filter' - // ) } - onClick = {action((e) => { - console.log("im here") - console.log( StrListCast(this.targetDoc._childFilters) - .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) - // ?.split(Doc.FilterSep)[2] === 'check' - ) - StrListCast(this.targetDoc._childFilters).find(filter => console.log(" rahhhhhh1 " + filter.split(Doc.FilterSep)[1] === facetValue)) - StrListCast(this.targetDoc._childFilters).find(filter => console.log(" rahhhhhh2 " + filter.split(Doc.FilterSep)[2]) ) - } - - // console.log("this is an experiment " + StrListCast(this.targetDoc._childFilters) - - ) - // undoable (e => console.log(" this is an experiment " + e.target.checked), 'set filter') - - } + onClick={undoable (e => + e.target.checked ? this._currentActiveFilters.set(fval, facetHeader) : this._currentActiveFilters.delete(facetHeader) , 'set filter' + ) } + + /> {facetValue}
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5a8a6e4b6..770a72855 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1437,6 +1437,7 @@ export namespace Doc { // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { + console.log("in setDocFilter: key "+ key + "value " + value) if (!container) return; const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; const childFilters = StrListCast(container[filterField]); -- cgit v1.2.3-70-g09d2 From ae1c61907b6ffd0f9949d0d697bdc941eb28b7e2 Mon Sep 17 00:00:00 2001 From: eperelm2 Date: Mon, 31 Jul 2023 15:41:53 -0400 Subject: filters - working on range --- src/client/views/FilterPanel.tsx | 16 ++++++++++------ src/fields/Doc.ts | 4 ++++ 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index f85052ff2..d6638df46 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -164,12 +164,16 @@ export class FilterPanel extends React.Component { this._chosenFacets.set(facetHeader, 'text'); } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { - // const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); - // const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); - // const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); - // newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; - // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; - // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; + console.log("in this IF STATEMENE ") + + const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); + const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); + const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); + // newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; + // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; + // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; + Doc.setDocRangeFilter(this.targetDoc, facetHeader) + // newFacet = Docs.Create.SliderDocument({ // title: facetHeader, diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 203f455db..578ee1caa 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1416,14 +1416,18 @@ export namespace Doc { } export function setDocRangeFilter(container: Opt, key: string, range?: number[]) { if (!container) return; + const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); + console.log("hellllloooooooooooooooooooooooooo " + childFiltersByRanges.length) for (let i = 0; i < childFiltersByRanges.length; i += 3) { + console.log("inside if statement") if (childFiltersByRanges[i] === key) { childFiltersByRanges.splice(i, 3); break; } } if (range !== undefined) { + console.log("in doc.ts in set range filter") childFiltersByRanges.push(key); childFiltersByRanges.push(range[0].toString()); childFiltersByRanges.push(range[1].toString()); -- cgit v1.2.3-70-g09d2 From d9f9eb5a89b14a3f9f6847ecd405c2142058b0d5 Mon Sep 17 00:00:00 2001 From: eperelm2 Date: Tue, 1 Aug 2023 16:04:46 -0400 Subject: filter - made bobs comments with basic slider implementation --- src/client/views/FilterPanel.tsx | 349 ++++++++++++++++++++++++++------------- src/fields/Doc.ts | 4 +- 2 files changed, 233 insertions(+), 120 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index ce6e2b1f3..b7718c6a3 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -13,6 +13,8 @@ import { SearchBox } from './search/SearchBox'; import { undoable } from '../util/UndoManager'; import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai'; import { CiCircleRemove } from 'react-icons/ci'; +import { Slider, Rail, Handles, Tracks, Ticks } from 'react-compound-slider'; +import { TooltipRail, Handle, Tick, Track } from './nodes/SliderBox-components'; //slight bug when you don't click on background canvas before creating filter and the you click on the canvas @@ -77,6 +79,14 @@ export class FilterPanel extends React.Component { return StrListCast(this.targetDoc?._childFilters).concat(this.rangeFilters); } + @computed get mapActiveFiltersToFacets() { + const filters = new Map(); + //this.targetDoc.docFilters + this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1] ,filter.split(Doc.FilterSep)[0] )) + console.log("what is wrong with filters " +filters ) + return filters + } + // // activeFacetHeaders() - just the facet names, not the rest of the filter // @@ -86,20 +96,31 @@ export class FilterPanel extends React.Component { // ["#tags", "width", "height"] // - @computed get currentActiveFilterz(){ // this.existingFilters - const filters = new Map(); - //this.targetDoc.docFilters - this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1] ,filter.split(Doc.FilterSep)[0] )) - console.log("what is wrong with filters " +filters ) - return filters + + @computed get activeFacetHeaders() { + const activeHeaders = new Array() + + this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]) ) + + + return activeHeaders; } + + + // @computed get currentActiveFilterz(){ // this.existingFilters + // const filters = new Map(); + // //this.targetDoc.docFilters + // this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1] ,filter.split(Doc.FilterSep)[0] )) + // console.log("what is wrong with filters " +filters ) + // return filters + // } /** * @returns a string array of the current attributes */ - @computed get currentFacets() { - return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); - } + // @computed get currentFacets() { + // return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); + // } gatherFieldValues(childDocs: Doc[], facetKey: string) { const valueSet = new Set(); @@ -149,28 +170,68 @@ export class FilterPanel extends React.Component { // Map("tags" => {"checkbox"}, // "width" => {"rangs", domain:[1978,1992]}) // - @computed get activeFacets() { // selectedFacetRenderInfo => []:{facetName, renderInfo}[] renderInfo: { renderType: text|range|... , minVal?: , maxVal?; } - - // return new Set( - // Array.from(new Set(Array.from(this.selectedFacetHeaders).concat(this.existingFacetHeaders))) - // .map(facetHeader => { --> getting exisitng filters and new filters that havent been selected but need to remove duplicates - // most of what facetClick did before ... - // minVal, maxvval... - // if (...) - // return {key: facet, renderType: "text"} - // else if (... numbers) return {key:facet, renderrType: range, extendedMinVal, maxval - // return - // return .. - // })) - - // console.log("chosen collpase " + this._chosenFacetsCollapse) - const facets = new Map(); - this.activeFilters.map(filter => facets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')); - // setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')))); - return facets; + + @computed get activeRenderedFacetInfos(){ + + return new Set( + Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map( + facetHeader => { + + const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); + + let nonNumbers = 0; + let minVal = Number.MAX_VALUE, + maxVal = -Number.MAX_VALUE; + facetValues.strings.map(val => { + const num = val ? Number(val) : Number.NaN; + if (Number.isNaN(num)) { + val && nonNumbers++; + } else { + minVal = Math.min(num, minVal); + maxVal = Math.max(num, maxVal); + } + }); + + + if (facetHeader === 'text' ){ + return {facetHeader: facetHeader, renderType: 'text'} + + } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1){ + + const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); + const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); + const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter + return {facetHeader: facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged: [extendedMinVal, extendedMaxVal]} + + } else{ + return {facetHeader: facetHeader, renderType: 'checkbox'} + } + } + )) } - @observable selectedFacetHeaders = new Set(); + // @computed get activeFacets() { // selectedFacetRenderInfo => []:{facetName, renderInfo}[] renderInfo: { renderType: text|range|... , minVal?: , maxVal?; } + + // // return new Set( + // // Array.from(new Set(Array.from(this.selectedFacetHeaders).concat(this.existingFacetHeaders))) + // // .map(facetHeader => { --> getting exisitng filters and new filters that havent been selected but need to remove duplicates + // // most of what facetClick did before ... + // // minVal, maxvval... + // // if (...) + // // return {key: facet, renderType: "text"} + // // else if (... numbers) return {key:facet, renderrType: range, extendedMinVal, maxval + // // return + // // return .. + // // })) + + // // console.log("chosen collpase " + this._chosenFacetsCollapse) + // const facets = new Map(); + // this.activeFilters.map(filter => facets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')); + // // setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')))); + // return facets; + // } + + @observable _selectedFacetHeaders = new Set(); /** * user clicks on a filter facet because they want to see it. @@ -182,72 +243,74 @@ export class FilterPanel extends React.Component { @action facetClick = (facetHeader: string) => { // just when someone chooses a facet - // return; + this._selectedFacetHeaders.add(facetHeader); + return - if (!this.targetDoc) return; - const allCollectionDocs = this.targetDocChildren; - const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); - let nonNumbers = 0; - let minVal = Number.MAX_VALUE, - maxVal = -Number.MAX_VALUE; - facetValues.strings.map(val => { - const num = val ? Number(val) : Number.NaN; - if (Number.isNaN(num)) { - val && nonNumbers++; - } else { - minVal = Math.min(num, minVal); - maxVal = Math.max(num, maxVal); - } - }); - if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { - // this._chosenFacets.set(facetHeader, 'text'); - } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { - - console.log("in this IF STATEMENE ") - - const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter - const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); - const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); - // this.targetDoc["year-min"] = 1978 - // // facetHeader == year - // this.targetDoc[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; - // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; - // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; - Doc.setDocRangeFilter(this.targetDoc, facetHeader, [extendedMinVal, extendedMaxVal] ) - - - // newFacet = Docs.Create.SliderDocument({ - // title: facetHeader, - // system: true, - // target: targetDoc, - // _fitWidth: true, - // _height: 40, - // _stayInCollection: true, - // treeViewExpandedView: 'layout', - // _treeViewOpen: true, - // _forceActive: true, - // _overflow: 'visible', - // }); - // const newFacetField = Doc.LayoutFieldKey(newFacet); - // const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); - // Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - // const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); - // const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); - // newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; - // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; - // Doc.GetProto(newFacet)[newFacetField + '-minThumb'] = extendedMinVal; - // Doc.GetProto(newFacet)[newFacetField + '-maxThumb'] = extendedMaxVal; - // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; - // newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: 'number' }); - // newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`); + // if (!this.targetDoc) return; + // const allCollectionDocs = this.targetDocChildren; + // const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); + + // let nonNumbers = 0; + // let minVal = Number.MAX_VALUE, + // maxVal = -Number.MAX_VALUE; + // facetValues.strings.map(val => { + // const num = val ? Number(val) : Number.NaN; + // if (Number.isNaN(num)) { + // val && nonNumbers++; + // } else { + // minVal = Math.min(num, minVal); + // maxVal = Math.max(num, maxVal); + // } + // }); + // if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { + // // this._chosenFacets.set(facetHeader, 'text'); + // } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { + + // console.log("in this IF STATEMENE ") + + // const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter + // const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); + // const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); + // // this.targetDoc["year-min"] = 1978 + // // // facetHeader == year + // // this.targetDoc[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; + // // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; + // // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; + // Doc.setDocRangeFilter(this.targetDoc, facetHeader, [extendedMinVal, extendedMaxVal] ) + + + // // newFacet = Docs.Create.SliderDocument({ + // // title: facetHeader, + // // system: true, + // // target: targetDoc, + // // _fitWidth: true, + // // _height: 40, + // // _stayInCollection: true, + // // treeViewExpandedView: 'layout', + // // _treeViewOpen: true, + // // _forceActive: true, + // // _overflow: 'visible', + // // }); + // // const newFacetField = Doc.LayoutFieldKey(newFacet); + // // const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); + // // Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox + // // const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); + // // const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); + // // newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; + // // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; + // // Doc.GetProto(newFacet)[newFacetField + '-minThumb'] = extendedMinVal; + // // Doc.GetProto(newFacet)[newFacetField + '-maxThumb'] = extendedMaxVal; + // // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; + // // newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: 'number' }); + // // newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`); - } else { - // this._chosenFacets.set(facetHeader, 'checkbox'); - } - this._chosenFacetsCollapse.set(facetHeader, false) + // } else { + // // this._chosenFacets.set(facetHeader, 'checkbox'); + // } + // this._chosenFacetsCollapse.set(facetHeader, false) }; @@ -256,12 +319,13 @@ export class FilterPanel extends React.Component { this._collapseReturnKeys.splice(0) - for (var key of this.facetValues(facetHeader)){ + for (var key of this.facetValues(facetHeader)){ // all filters currently set // console.log("key : " + key ) - if (this.currentActiveFilterz.get(key)){ + if (this.mapActiveFiltersToFacets.get(key)){ // work with the current filter selected // console.log("WEREEE HERHEHHHHEHHHHEEE") this._collapseReturnKeys.push(key) - }} + } + } return (
@@ -301,7 +365,8 @@ export class FilterPanel extends React.Component { render() { - const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); + // const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); + const options = this._allFacets.filter(facet => this.activeFacetHeaders.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); return (
@@ -322,41 +387,42 @@ export class FilterPanel extends React.Component {
- {Array.from(this.activeFacets.keys()).map(facetHeader => ( // iterato over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader + {Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => ( // iterato over activeFacetRenderInfos ==> renderInfo which you can renderInfo.facetHeader
- {facetHeader.charAt(0).toUpperCase() + facetHeader.slice(1)} + {renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)}
{ - for (const [key, value] of this.currentActiveFilterz) { - - console.log("NEW KEY " + key + "NEW VAL " + value); - - } - console.log("this is gather field values : " + this.gatherFieldValues(this.targetDocChildren, facetHeader)) - console.log("this is current facets: " + this.currentFacets) - - console.log("this is facet Header " + facetHeader + ". this is the get " + this.activeFacets.get(facetHeader)) - const collapseBoolValue = this._chosenFacetsCollapse.get(facetHeader) - this._chosenFacetsCollapse.set(facetHeader, !collapseBoolValue )})}> + + const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader) + this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue )})}> - {this._chosenFacetsCollapse.get(facetHeader) ? : } + {this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? : }
{ - for (var key of this.facetValues(facetHeader)){ - if (this.currentActiveFilterz.get(key)){ - Doc.setDocFilter(this.targetDoc, facetHeader, key, 'remove') + for (var key of this.facetValues(renderInfo.facetHeader)){ + if (this.mapActiveFiltersToFacets.get(key)){ + Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove') }} - this.activeFacets.delete(facetHeader) + if (renderInfo.facetHeader) + + this._selectedFacetHeaders.delete(renderInfo.facetHeader) + + // this.activeFacets.delete(renderInfo.facetHeader) // this._chosenFacets.delete(facetHeader) - this._chosenFacetsCollapse.delete(facetHeader) + this._chosenFacetsCollapse.delete(renderInfo.facetHeader) + + console.log("this is activeFilters " + this.activeFilters) + console.log("this is activeFacetHeaders " + this.activeFacetHeaders) + console.log("thsi is activeRenderedFacetInfos " + this.activeRenderedFacetInfos) + console.log("thsi is selected facet Headers " + this._selectedFacetHeaders ) })} > @@ -365,11 +431,11 @@ export class FilterPanel extends React.Component {
- { this._chosenFacetsCollapse.get(facetHeader) ? + { this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? //
{this.sortingCurrentFacetValues(facetHeader)}
&& this._collapseReturnKeys.splice(0) - this.sortingCurrentFacetValues(facetHeader) + this.sortingCurrentFacetValues(renderInfo.facetHeader) // && this._collapseReturnKeys.splice(0) - : this.displayFacetValueFilterUIs(this.activeFacets.get(facetHeader), facetHeader) } // pass renderInfo from iterator + : this.displayFacetValueFilterUIs(renderInfo.renderType, renderInfo.facetHeader, renderInfo.domain, renderInfo.range ) } {/* */}
))} @@ -378,7 +444,7 @@ export class FilterPanel extends React.Component { ); } - private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string): React.ReactNode { + private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string, renderInfoDomain?: number[] | undefined, renderInfoRange?: number[] ): React.ReactNode { // displayFacetValueFilterUIs(renderIinfo) switch (type /* renderInfo.type */ ) { case 'text': // if (this.chosenFacets.get(facetHeader) === 'text') @@ -417,6 +483,55 @@ export class FilterPanel extends React.Component {
); }) + + case 'range': + console.log("in range") + + const domain = renderInfoDomain; + + if (domain){ + return( + Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)} values={renderInfoRange!} > + {railProps => } + + {({ handles, activeHandleID, getHandleProps }) => ( +
+ {handles.map((handle, i) => { + // const value = i === 0 ? defaultValues[0] : defaultValues[1]; + return ( +
+ +
+ ); + })} +
+ )} +
+ + {({ tracks, getTrackProps }) => ( +
+ {tracks.map(({ id, source, target }) => ( + + ))} +
+ )} +
+ + {({ ticks }) => ( +
+ {ticks.map(tick => ( + val.toString()} /> + ))} +
+ )} +
+
+ ) + } + + + // case 'range' // return domain is number[] for min and max diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 578ee1caa..d07028ec2 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1414,11 +1414,10 @@ export namespace Doc { prevLayout === 'icon' && (doc.deiconifyLayout = undefined); doc.layout_fieldKey = deiconify || 'layout'; } - export function setDocRangeFilter(container: Opt, key: string, range?: number[]) { + export function setDocRangeFilter(container: Opt, key: string, range?: readonly number[]) { if (!container) return; const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); - console.log("hellllloooooooooooooooooooooooooo " + childFiltersByRanges.length) for (let i = 0; i < childFiltersByRanges.length; i += 3) { console.log("inside if statement") if (childFiltersByRanges[i] === key) { @@ -1441,7 +1440,6 @@ export namespace Doc { // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { - console.log("in setDocFilter: key "+ key + "value " + value) if (!container) return; const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; const childFilters = StrListCast(container[filterField]); -- cgit v1.2.3-70-g09d2 From 8d85780411df77db6150b88f0d8272f495c7fdb1 Mon Sep 17 00:00:00 2001 From: eperelm2 Date: Wed, 2 Aug 2023 12:58:34 -0400 Subject: filter - slider works (w/o UI and collapse) --- src/client/views/FilterPanel.scss | 22 ++++ src/client/views/FilterPanel.tsx | 228 ++++++++++---------------------------- src/fields/Doc.ts | 19 +++- 3 files changed, 99 insertions(+), 170 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/FilterPanel.scss b/src/client/views/FilterPanel.scss index 78e7904b8..b18b01325 100644 --- a/src/client/views/FilterPanel.scss +++ b/src/client/views/FilterPanel.scss @@ -231,6 +231,28 @@ } +// .sliderBox-outerDiv { +// width: 30%;// width: calc(100% - 14px); // 14px accounts for handles that are at the max value of the slider that would extend outside the box +// height: 40; // height: 100%; +// border-radius: inherit; +// display: flex; +// flex-direction: column; +// position: relative; +// // background-color: yellow; +// .slider-tracks { +// top: 7px; +// position: relative; +// } +// .slider-ticks { +// position: relative; +// } +// .slider-handles { +// top: 7px; +// position: relative; +// } +// } + + diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index b7718c6a3..54f5122b4 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -83,7 +83,6 @@ export class FilterPanel extends React.Component { const filters = new Map(); //this.targetDoc.docFilters this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1] ,filter.split(Doc.FilterSep)[0] )) - console.log("what is wrong with filters " +filters ) return filters } @@ -99,22 +98,11 @@ export class FilterPanel extends React.Component { @computed get activeFacetHeaders() { const activeHeaders = new Array() - this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]) ) return activeHeaders; } - - - // @computed get currentActiveFilterz(){ // this.existingFilters - // const filters = new Map(); - // //this.targetDoc.docFilters - // this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1] ,filter.split(Doc.FilterSep)[0] )) - // console.log("what is wrong with filters " +filters ) - // return filters - // } - /** * @returns a string array of the current attributes */ @@ -210,27 +198,6 @@ export class FilterPanel extends React.Component { )) } - // @computed get activeFacets() { // selectedFacetRenderInfo => []:{facetName, renderInfo}[] renderInfo: { renderType: text|range|... , minVal?: , maxVal?; } - - // // return new Set( - // // Array.from(new Set(Array.from(this.selectedFacetHeaders).concat(this.existingFacetHeaders))) - // // .map(facetHeader => { --> getting exisitng filters and new filters that havent been selected but need to remove duplicates - // // most of what facetClick did before ... - // // minVal, maxvval... - // // if (...) - // // return {key: facet, renderType: "text"} - // // else if (... numbers) return {key:facet, renderrType: range, extendedMinVal, maxval - // // return - // // return .. - // // })) - - // // console.log("chosen collpase " + this._chosenFacetsCollapse) - // const facets = new Map(); - // this.activeFilters.map(filter => facets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')); - // // setTimeout(() => StrListCast(this.targetDoc?._childFilters).map(action(filter => this._chosenFacets.set(filter.split(Doc.FilterSep)[0], filter.split(Doc.FilterSep)[2] === 'match' ? 'text' : 'checkbox')))); - // return facets; - // } - @observable _selectedFacetHeaders = new Set(); /** @@ -240,105 +207,40 @@ export class FilterPanel extends React.Component { * * // this._selectedFacets.add(facetHeader); .. add to Set() not array */ + @action facetClick = (facetHeader: string) => { // just when someone chooses a facet this._selectedFacetHeaders.add(facetHeader); return - - - // if (!this.targetDoc) return; - // const allCollectionDocs = this.targetDocChildren; - // const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); - - // let nonNumbers = 0; - // let minVal = Number.MAX_VALUE, - // maxVal = -Number.MAX_VALUE; - // facetValues.strings.map(val => { - // const num = val ? Number(val) : Number.NaN; - // if (Number.isNaN(num)) { - // val && nonNumbers++; - // } else { - // minVal = Math.min(num, minVal); - // maxVal = Math.max(num, maxVal); - // } - // }); - // if (facetHeader === 'text' || (facetValues.rtFields / allCollectionDocs.length > 0.1 && facetValues.strings.length > 20)) { - // // this._chosenFacets.set(facetHeader, 'text'); - // } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { - - // console.log("in this IF STATEMENE ") - - // const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter - // const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); - // const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); - // // this.targetDoc["year-min"] = 1978 - // // // facetHeader == year - // // this.targetDoc[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; - // // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; - // // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; - // Doc.setDocRangeFilter(this.targetDoc, facetHeader, [extendedMinVal, extendedMaxVal] ) - - - // // newFacet = Docs.Create.SliderDocument({ - // // title: facetHeader, - // // system: true, - // // target: targetDoc, - // // _fitWidth: true, - // // _height: 40, - // // _stayInCollection: true, - // // treeViewExpandedView: 'layout', - // // _treeViewOpen: true, - // // _forceActive: true, - // // _overflow: 'visible', - // // }); - // // const newFacetField = Doc.LayoutFieldKey(newFacet); - // // const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); - // // Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - // // const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); - // // const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); - // // newFacet[newFacetField + '-min'] = ranged === undefined ? extendedMinVal : ranged[0]; - // // newFacet[newFacetField + '-max'] = ranged === undefined ? extendedMaxVal : ranged[1]; - // // Doc.GetProto(newFacet)[newFacetField + '-minThumb'] = extendedMinVal; - // // Doc.GetProto(newFacet)[newFacetField + '-maxThumb'] = extendedMaxVal; - // // const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; - // // newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: 'number' }); - // // newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`); - - - // } else { - // // this._chosenFacets.set(facetHeader, 'checkbox'); - // } - // this._chosenFacetsCollapse.set(facetHeader, false) }; @action sortingCurrentFacetValues = (facetHeader:string) => { - + console.log("in function to begin with") this._collapseReturnKeys.splice(0) - for (var key of this.facetValues(facetHeader)){ // all filters currently set - // console.log("key : " + key ) - if (this.mapActiveFiltersToFacets.get(key)){ // work with the current filter selected - // console.log("WEREEE HERHEHHHHEHHHHEEE") - this._collapseReturnKeys.push(key) - } - } + console.log("this si sfacetValies " + this.facetValues(facetHeader)) - return (
- - {this._collapseReturnKeys.join(', ') } - {/* .toString()} */} -
) - + //if range then display range values + + // if(Array.from(this.activeRenderedFacetInfos.keys()).map(renderInfo => (renderInfo.renderType === "range" && renderInfo.facetHeader === facetHeader ))){ + // console.log("JOE MAMA") + // } + for (var key of this.facetValues(facetHeader)){ + if (this.mapActiveFiltersToFacets.get(key)){ + this._collapseReturnKeys.push(key) + }} + return ( +
+ {this._collapseReturnKeys.join(', ') } +
) } - - facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchBox.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); @@ -365,7 +267,6 @@ export class FilterPanel extends React.Component { render() { - // const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); const options = this._allFacets.filter(facet => this.activeFacetHeaders.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); return ( @@ -410,19 +311,20 @@ export class FilterPanel extends React.Component { if (this.mapActiveFiltersToFacets.get(key)){ Doc.setDocFilter(this.targetDoc, renderInfo.facetHeader, key, 'remove') }} - - if (renderInfo.facetHeader) - this._selectedFacetHeaders.delete(renderInfo.facetHeader) - - // this.activeFacets.delete(renderInfo.facetHeader) - // this._chosenFacets.delete(facetHeader) this._chosenFacetsCollapse.delete(renderInfo.facetHeader) + Doc.setDocRangeFilter(this.targetDoc, renderInfo.facetHeader, renderInfo.domain, 'remove') + + console.log("this is activeFilters " + this.activeFilters) console.log("this is activeFacetHeaders " + this.activeFacetHeaders) console.log("thsi is activeRenderedFacetInfos " + this.activeRenderedFacetInfos) console.log("thsi is selected facet Headers " + this._selectedFacetHeaders ) + console.log("THIS IS THE ONE ADDED "+ this.targetDoc?._childFiltersByRanges) + + + })} > @@ -432,9 +334,7 @@ export class FilterPanel extends React.Component {
{ this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? - //
{this.sortingCurrentFacetValues(facetHeader)}
&& this._collapseReturnKeys.splice(0) this.sortingCurrentFacetValues(renderInfo.facetHeader) - // && this._collapseReturnKeys.splice(0) : this.displayFacetValueFilterUIs(renderInfo.renderType, renderInfo.facetHeader, renderInfo.domain, renderInfo.range ) } {/* */}
@@ -445,7 +345,6 @@ export class FilterPanel extends React.Component { } private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string, renderInfoDomain?: number[] | undefined, renderInfoRange?: number[] ): React.ReactNode { - // displayFacetValueFilterUIs(renderIinfo) switch (type /* renderInfo.type */ ) { case 'text': // if (this.chosenFacets.get(facetHeader) === 'text') return ( @@ -460,9 +359,7 @@ export class FilterPanel extends React.Component { /> ); case 'checkbox': - return this.facetValues(facetHeader).map(fval => { - const facetValue = fval; return (
@@ -472,12 +369,9 @@ export class FilterPanel extends React.Component { StrListCast(this.targetDoc._childFilters) .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) ?.split(Doc.FilterSep)[2] === 'check' - } type={type} onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} - - /> {facetValue}
@@ -485,48 +379,48 @@ export class FilterPanel extends React.Component { }) case 'range': - console.log("in range") - const domain = renderInfoDomain; - if (domain){ return( - Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)} values={renderInfoRange!} > - {railProps => } - - {({ handles, activeHandleID, getHandleProps }) => ( -
- {handles.map((handle, i) => { - // const value = i === 0 ? defaultValues[0] : defaultValues[1]; - return ( -
- +
+ Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)} values={renderInfoRange!} > + {railProps => } + + {({ handles, activeHandleID, getHandleProps }) => ( +
+ {handles.map((handle, i) => { + // const value = i === 0 ? defaultValues[0] : defaultValues[1]; + return ( +
+ +
+ ); + })} +
+ )} +
+ + {({ tracks, getTrackProps }) => ( +
+ {tracks.map(({ id, source, target }) => ( + + ))}
- ); - })} -
- )} - - - {({ tracks, getTrackProps }) => ( -
- {tracks.map(({ id, source, target }) => ( - - ))} -
- )} -
- - {({ ticks }) => ( -
- {ticks.map(tick => ( - val.toString()} /> - ))} -
- )} -
- + )} + + + {({ ticks }) => ( +
+ {ticks.map(tick => ( + val.toString()} /> + ))} +
+ )} +
+ +
+ ) } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index d07028ec2..84b1705bc 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1414,14 +1414,19 @@ export namespace Doc { prevLayout === 'icon' && (doc.deiconifyLayout = undefined); doc.layout_fieldKey = deiconify || 'layout'; } - export function setDocRangeFilter(container: Opt, key: string, range?: readonly number[]) { + export function setDocRangeFilter(container: Opt, key: string, range?: readonly number[], modifiers?: 'remove') { //, modifiers: 'remove' | 'set' if (!container) return; - + const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); + + + + for (let i = 0; i < childFiltersByRanges.length; i += 3) { - console.log("inside if statement") if (childFiltersByRanges[i] === key) { + console.log("this is key inside childfilters by range " + key) childFiltersByRanges.splice(i, 3); + console.log("this is child filters by range " + childFiltersByRanges) break; } } @@ -1431,7 +1436,15 @@ export namespace Doc { childFiltersByRanges.push(range[0].toString()); childFiltersByRanges.push(range[1].toString()); container._childFiltersByRanges = new List(childFiltersByRanges); + console.log("this is child filters by range "+ childFiltersByRanges[0] + "," + childFiltersByRanges[1] + "," + childFiltersByRanges[2]) + console.log("this is new list " + container._childFiltersByRange) + } + + if (modifiers){ + childFiltersByRanges.splice(0,3) + container._childFiltersByRanges = new List(childFiltersByRanges); } + console.log("this is child filters by range END"+ childFiltersByRanges[0] + "," + childFiltersByRanges[1] + "," + childFiltersByRanges[2]) } export const FilterSep = '::'; -- cgit v1.2.3-70-g09d2 From 1c5bb5390ab9f198acde7d48aaa7d7d536f432cd Mon Sep 17 00:00:00 2001 From: eperelm2 Date: Thu, 10 Aug 2023 18:46:04 -0400 Subject: need to pull --- package-lock.json | 39 --- src/.DS_Store | Bin 10244 -> 10244 bytes src/client/documents/Documents.ts | 11 +- src/client/util/.ClientUtils.ts.icloud | Bin 0 -> 161 bytes src/client/util/.ReportManager.scss.icloud | Bin 0 -> 168 bytes src/client/util/.ReportManager.tsx.icloud | Bin 0 -> 167 bytes src/client/views/.Palette.scss.icloud | Bin 0 -> 160 bytes src/client/views/DashboardView.tsx | 78 +++--- src/client/views/FilterPanel.tsx | 371 ++++++++++++++------------- src/client/views/Palette.tsx | 69 +++++ src/client/views/nodes/.QueryBox.scss.icloud | Bin 0 -> 160 bytes src/client/views/nodes/.QueryBox.tsx.icloud | Bin 0 -> 160 bytes src/fields/.ListSpec.ts.icloud | Bin 0 -> 158 bytes src/fields/IconField.ts | 26 ++ src/fields/PresField.ts | 6 + src/mobile/MobileInterface.tsx | 1 + src/server/stats/.userLoginStats.csv.icloud | Bin 0 -> 168 bytes 17 files changed, 329 insertions(+), 272 deletions(-) create mode 100644 src/client/util/.ClientUtils.ts.icloud create mode 100644 src/client/util/.ReportManager.scss.icloud create mode 100644 src/client/util/.ReportManager.tsx.icloud create mode 100644 src/client/views/.Palette.scss.icloud create mode 100644 src/client/views/Palette.tsx create mode 100644 src/client/views/nodes/.QueryBox.scss.icloud create mode 100644 src/client/views/nodes/.QueryBox.tsx.icloud create mode 100644 src/fields/.ListSpec.ts.icloud create mode 100644 src/fields/IconField.ts create mode 100644 src/fields/PresField.ts create mode 100644 src/server/stats/.userLoginStats.csv.icloud (limited to 'src/fields') diff --git a/package-lock.json b/package-lock.json index e08568816..884a64a2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10225,16 +10225,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz", @@ -11615,28 +11605,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -11648,7 +11616,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -27407,12 +27374,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/src/.DS_Store b/src/.DS_Store index 06389d6ae..946e85928 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5d8ae19fc..533df5c11 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -74,6 +74,8 @@ export class FInfo { readOnly: boolean = false; fieldType?: string = ''; values?: Field[]; + + filterable?: boolean = true; // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string constructor(d: string, readOnly?: boolean) { @@ -88,7 +90,7 @@ class BoolInfo extends FInfo { class NumInfo extends FInfo { fieldType? = 'number'; values?: number[] = []; - constructor(d: string, readOnly?: boolean, values?: number[], filterable?:boolean) { + constructor(d: string, readOnly?: boolean, values?: number[], filterable?: boolean) { super(d, readOnly); this.values = values; } @@ -96,15 +98,16 @@ class NumInfo extends FInfo { class StrInfo extends FInfo { fieldType? = 'string'; values?: string[] = []; - constructor(d: string, readOnly?: boolean, values?: string[], filterable?:boolean) { + constructor(d: string, filterable?: boolean, readOnly?: boolean, values?: string[]) { super(d, readOnly); this.values = values; + this.filterable = filterable; } } class DocInfo extends FInfo { fieldType? = 'Doc'; values?: Doc[] = []; - constructor(d: string, filterable?:boolean, values?: Doc[] ) { + constructor(d: string, filterable?: boolean, values?: Doc[]) { super(d, true); this.values = values; } @@ -184,7 +187,7 @@ export class DocumentOptions { author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document', false); - color?: STRt = new StrInfo('foreground color data doc'); + color?: STRt = new StrInfo('foreground color data doc', true); hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection'); backgroundColor?: STRt = new StrInfo('background color for data doc'); opacity?: NUMt = new NumInfo('document opacity'); diff --git a/src/client/util/.ClientUtils.ts.icloud b/src/client/util/.ClientUtils.ts.icloud new file mode 100644 index 000000000..e5e477586 Binary files /dev/null and b/src/client/util/.ClientUtils.ts.icloud differ diff --git a/src/client/util/.ReportManager.scss.icloud b/src/client/util/.ReportManager.scss.icloud new file mode 100644 index 000000000..f5d34d292 Binary files /dev/null and b/src/client/util/.ReportManager.scss.icloud differ diff --git a/src/client/util/.ReportManager.tsx.icloud b/src/client/util/.ReportManager.tsx.icloud new file mode 100644 index 000000000..72924c53a Binary files /dev/null and b/src/client/util/.ReportManager.tsx.icloud differ diff --git a/src/client/views/.Palette.scss.icloud b/src/client/views/.Palette.scss.icloud new file mode 100644 index 000000000..49a2ac2da Binary files /dev/null and b/src/client/views/.Palette.scss.icloud differ diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index ae55c8ebf..d6c7b43d5 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -43,7 +43,7 @@ export class DashboardView extends React.Component { @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName: string | undefined = undefined; - @observable private newDashboardColor: string | undefined = "#AFAFAF"; + @observable private newDashboardColor: string | undefined = '#AFAFAF'; @action abortCreateNewDashboard = () => { this.newDashboardName = undefined; }; @@ -100,24 +100,17 @@ export class DashboardView extends React.Component { const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const placeholder = `Dashboard ${dashboardCount}`; return ( -
+ color: StrCast(Doc.UserDoc().userColor), + }}>
Create New Dashboard
- this.setNewDashboardName(val as string)} - fillWidth - /> + this.setNewDashboardName(val as string)} fillWidth /> { @@ -165,21 +158,14 @@ export class DashboardView extends React.Component { }; render() { - const color = StrCast(Doc.UserDoc().userColor) - const variant = StrCast(Doc.UserDoc().userVariantColor) + const color = StrCast(Doc.UserDoc().userColor); + const variant = StrCast(Doc.UserDoc().userVariantColor); return ( <>
- + + +
+
+

{this.questionPartOne}

+

{this.questionPartTwo}

+
+
+ {this.selectedQuestion.answerParts.includes('force of gravity') && ( + Gravity magnitude

} + lowerBound={0} + dataDoc={this.dataDoc} + prop="review_GravityMagnitude" + step={0.1} + unit={'N'} + upperBound={50} + value={NumCast(this.dataDoc.review_GravityMagnitude)} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of gravity')]} + labelWidth={'7em'} + /> + )} + {this.selectedQuestion.answerParts.includes('angle of gravity') && ( + Gravity angle

} + lowerBound={0} + dataDoc={this.dataDoc} + prop="review_GravityAngle" + step={1} + unit={'°'} + upperBound={360} + value={NumCast(this.dataDoc.review_GravityAngle)} + radianEquivalent={true} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of gravity')]} + labelWidth={'7em'} + /> + )} + {this.selectedQuestion.answerParts.includes('normal force') && ( + Normal force magnitude

} + lowerBound={0} + dataDoc={this.dataDoc} + prop="review_NormalMagnitude" + step={0.1} + unit={'N'} + upperBound={50} + value={NumCast(this.dataDoc.review_NormalMagnitude)} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('normal force')]} + labelWidth={'7em'} + /> + )} + {this.selectedQuestion.answerParts.includes('angle of normal force') && ( + Normal force angle

} + lowerBound={0} + dataDoc={this.dataDoc} + prop="review_NormalAngle" + step={1} + unit={'°'} + upperBound={360} + value={NumCast(this.dataDoc.review_NormalAngle)} + radianEquivalent={true} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of normal force')]} + labelWidth={'7em'} + /> + )} + {this.selectedQuestion.answerParts.includes('force of static friction') && ( + Static friction magnitude

} + lowerBound={0} + dataDoc={this.dataDoc} + prop="review_StaticMagnitude" + step={0.1} + unit={'N'} + upperBound={50} + value={NumCast(this.dataDoc.review_StaticMagnitude)} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('force of static friction')]} + labelWidth={'7em'} + /> + )} + {this.selectedQuestion.answerParts.includes('angle of static friction') && ( + Static friction angle

} + lowerBound={0} + dataDoc={this.dataDoc} + prop="review_StaticAngle" + step={1} + unit={'°'} + upperBound={360} + value={NumCast(this.dataDoc.review_StaticAngle)} + radianEquivalent={true} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('angle of static friction')]} + labelWidth={'7em'} + /> + )} + {this.selectedQuestion.answerParts.includes('coefficient of static friction') && ( + + μs + + } + lowerBound={0} + dataDoc={this.dataDoc} + prop="coefficientOfStaticFriction" + step={0.1} + unit={''} + upperBound={1} + value={NumCast(this.dataDoc.coefficientOfStaticFriction)} + effect={this.updateReviewForcesBasedOnCoefficient} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('coefficient of static friction')]} + /> + )} + {this.selectedQuestion.answerParts.includes('wedge angle') && ( + θ} + lowerBound={0} + dataDoc={this.dataDoc} + prop="wedge_angle" + step={1} + unit={'°'} + upperBound={49} + value={this.wedgeAngle} + effect={(val: number) => { + this.changeWedgeBasedOnNewAngle(val); + this.updateReviewForcesBasedOnAngle(val); + }} + radianEquivalent={true} + showIcon={BoolCast(this.dataDoc.simulation_showIcon)} + correctValue={NumListCast(this.dataDoc.answers)[this.selectedQuestion.answerParts.indexOf('wedge angle')]} + /> + )} +
+
+
+ )} + {this.simulationMode == 'Tutorial' && ( +
+
+

Problem

+

{this.tutorial.question}

+
+
+ { + let step = NumCast(this.dataDoc.tutorial_stepNumber) - 1; + step = Math.max(step, 0); + step = Math.min(step, this.tutorial.steps.length - 1); + this.dataDoc.tutorial_stepNumber = step; + this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces); + this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces); + this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude; + }} + disabled={this.dataDoc.tutorial_stepNumber == 0}> + {/* */} + +
+

+ Step {NumCast(this.dataDoc.tutorial_stepNumber) + 1}: {this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].description} +

+

{this.tutorial.steps[NumCast(this.dataDoc.tutorial_stepNumber)].content}

+
+ { + let step = NumCast(this.dataDoc.tutorial_stepNumber) + 1; + step = Math.max(step, 0); + step = Math.min(step, this.tutorial.steps.length - 1); + this.dataDoc.tutorial_stepNumber = step; + this.dataDoc.mass1_forcesStart = JSON.stringify(this.tutorial.steps[step].forces); + this.dataDoc.mass1_forcesUpdated = JSON.stringify(this.tutorial.steps[step].forces); + this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude; + }} + disabled={this.dataDoc.tutorial_stepNumber === this.tutorial.steps.length - 1}> + {/* */} + +
+
+ {(this.simulationType == 'One Weight' || this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') &&

Resources

} + {this.simulationType == 'One Weight' && ( + + )} + {this.simulationType == 'Inclined Plane' && ( + + )} + {this.simulationType == 'Pendulum' && ( + + )} +
+
+ )} + {this.simulationMode == 'Review' && this.simulationType == 'Inclined Plane' && ( +
+

(this.dataDoc.simulation_mode = 'Tutorial')}> + {' '} + Go to walkthrough{' '} +

+
+ + +
+
+ )} + {this.simulationMode == 'Freeform' && ( +
+ + + {this.simulationType == 'One Weight' && ( + (this.dataDoc.elasticCollisions = !this.dataDoc.elasticCollisions)} />} + label="Make collisions elastic" + labelPlacement="start" + /> + )} + (this.dataDoc.simulation_showForces = !this.dataDoc.simulation_showForces)} />} + label="Show force vectors" + labelPlacement="start" + /> + {(this.simulationType == 'Inclined Plane' || this.simulationType == 'Pendulum') && ( + (this.dataDoc.simulation_showComponentForces = !this.dataDoc.simulation_showComponentForces)} />} + label="Show component force vectors" + labelPlacement="start" + /> + )} + (this.dataDoc.simulation_showAcceleration = !this.dataDoc.simulation_showAcceleration)} />} + label="Show acceleration vector" + labelPlacement="start" + /> + (this.dataDoc.simulation_showVelocity = !this.dataDoc.simulation_showVelocity)} />} + label="Show velocity vector" + labelPlacement="start" + /> + Speed} lowerBound={1} dataDoc={this.dataDoc} prop="simulation_speed" step={1} unit={'x'} upperBound={10} value={NumCast(this.dataDoc.simulation_speed, 2)} labelWidth={'5em'} /> + {this.dataDoc.simulation_paused && this.simulationType != 'Circular Motion' && ( + Gravity} + lowerBound={-30} + dataDoc={this.dataDoc} + prop="gravity" + step={0.01} + unit={'m/s2'} + upperBound={0} + value={NumCast(this.dataDoc.simulation_gravity, -9.81)} + effect={(val: number) => this.setupSimulation()} + labelWidth={'5em'} + /> + )} + {this.dataDoc.simulation_paused && this.simulationType != 'Pulley' && ( + Mass} + lowerBound={1} + dataDoc={this.dataDoc} + prop="mass1" + step={0.1} + unit={'kg'} + upperBound={5} + value={this.mass1 ?? 1} + effect={(val: number) => this.setupSimulation()} + labelWidth={'5em'} + /> + )} + {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && ( + Red mass} + lowerBound={1} + dataDoc={this.dataDoc} + prop="mass1" + step={0.1} + unit={'kg'} + upperBound={5} + value={this.mass1 ?? 1} + effect={(val: number) => this.setupSimulation()} + labelWidth={'5em'} + /> + )} + {this.dataDoc.simulation_paused && this.simulationType == 'Pulley' && ( + Blue mass} + lowerBound={1} + dataDoc={this.dataDoc} + prop="mass2" + step={0.1} + unit={'kg'} + upperBound={5} + value={this.mass2 ?? 1} + effect={(val: number) => this.setupSimulation()} + labelWidth={'5em'} + /> + )} + {this.dataDoc.simulation_paused && this.simulationType == 'Circular Motion' && ( + Rod length} + lowerBound={100} + dataDoc={this.dataDoc} + prop="circularMotionRadius" + step={5} + unit={'m'} + upperBound={250} + value={this.circularMotionRadius} + effect={(val: number) => this.setupSimulation()} + labelWidth={'5em'} + /> + )} + + + {this.simulationType == 'Spring' && this.dataDoc.simulation_paused && ( +
+ Spring stiffness} + lowerBound={0.1} + dataDoc={this.dataDoc} + prop="spring_constant" + step={1} + unit={'N/m'} + upperBound={500} + value={this.springConstant} + effect={action(() => this._simReset++)} + radianEquivalent={false} + mode={'Freeform'} + labelWidth={'7em'} + /> + Rest length} + lowerBound={10} + dataDoc={this.dataDoc} + prop="spring_lengthRest" + step={100} + unit="" + upperBound={500} + value={this.springLengthRest} + effect={action(() => this._simReset++)} + radianEquivalent={false} + mode="Freeform" + labelWidth={'7em'} + /> + Starting displacement} + lowerBound={-(this.springLengthRest - 10)} + dataDoc={this.dataDoc} + prop="" + step={10} + unit="" + upperBound={this.springLengthRest} + value={this.springLengthStart - this.springLengthRest} + effect={action((val: number) => { + this.dataDoc.mass1_positionYstart = this.springLengthRest + val; + this.dataDoc.spring_lengthStart = this.springLengthRest + val; + this._simReset++; + })} + radianEquivalent={false} + mode="Freeform" + labelWidth={'7em'} + /> +
+ )} + {this.simulationType == 'Inclined Plane' && this.dataDoc.simulation_paused && ( +
+ θ} + lowerBound={0} + dataDoc={this.dataDoc} + prop="wedge_angle" + step={1} + unit={'°'} + upperBound={49} + value={this.wedgeAngle} + effect={action((val: number) => { + this.changeWedgeBasedOnNewAngle(val); + this._simReset++; + })} + radianEquivalent={true} + mode={'Freeform'} + labelWidth={'2em'} + /> + + μs + + } + lowerBound={0} + dataDoc={this.dataDoc} + prop="coefficientOfStaticFriction" + step={0.1} + unit={''} + upperBound={1} + value={NumCast(this.dataDoc.coefficientOfStaticFriction) ?? 0} + effect={action((val: number) => { + this.updateForcesWithFriction(val); + if (val < NumCast(this.dataDoc.coefficientOfKineticFriction)) { + this.dataDoc.soefficientOfKineticFriction = val; + } + this._simReset++; + })} + mode={'Freeform'} + labelWidth={'2em'} + /> + + μk + + } + lowerBound={0} + dataDoc={this.dataDoc} + prop="coefficientOfKineticFriction" + step={0.1} + unit={''} + upperBound={NumCast(this.dataDoc.coefficientOfStaticFriction)} + value={NumCast(this.dataDoc.coefficientOfKineticFriction) ?? 0} + effect={action(() => this._simReset++)} + mode={'Freeform'} + labelWidth={'2em'} + /> +
+ )} + {this.simulationType == 'Inclined Plane' && !this.dataDoc.simulation_paused && ( + + <> + θ: {Math.round(this.wedgeAngle * 100) / 100}° ≈ {Math.round(((this.wedgeAngle * Math.PI) / 180) * 100) / 100} rad +
+ μ s: {this.dataDoc.coefficientOfStaticFriction} +
+ μ k: {this.dataDoc.coefficientOfKineticFriction} + +
+ )} + {this.simulationType == 'Pendulum' && !this.dataDoc.simulation_paused && ( + + θ: {Math.round(this.pendulumAngle * 100) / 100}° ≈ {Math.round(((this.pendulumAngle * Math.PI) / 180) * 100) / 100} rad + + )} + {this.simulationType == 'Pendulum' && this.dataDoc.simulation_paused && ( +
+ Angle} + lowerBound={0} + dataDoc={this.dataDoc} + prop="pendulum_angle" + step={1} + unit={'°'} + upperBound={59} + value={NumCast(this.dataDoc.pendulum_angle, 30)} + effect={action(value => { + this.dataDoc.pendulum_angleStart = value; + this.dataDoc.pendulum_lengthStart = this.dataDoc.pendulum_length; + if (this.simulationType == 'Pendulum') { + const mag = this.mass1 * Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180); + + const forceOfTension: IForce = { + description: 'Tension', + magnitude: mag, + directionInDegrees: 90 - value, + }; + const gravityParallel: IForce = { + description: 'Gravity Parallel Component', + magnitude: Math.abs(this.gravity) * Math.cos((value * Math.PI) / 180), + directionInDegrees: 270 - value, + }; + const gravityPerpendicular: IForce = { + description: 'Gravity Perpendicular Component', + magnitude: Math.abs(this.gravity) * Math.sin((value * Math.PI) / 180), + directionInDegrees: -value, + }; + + const length = this.pendulumLength; + const x = length * Math.cos(((90 - value) * Math.PI) / 180); + const y = length * Math.sin(((90 - value) * Math.PI) / 180); + const xPos = this.xMax / 2 - x - NumCast(this.dataDoc.radius); + const yPos = y - NumCast(this.dataDoc.radius) - 5; + this.dataDoc.mass1_positionXstart = xPos; + this.dataDoc.mass1_positionYstart = yPos; + + this.dataDoc.mass1_forcesStart = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); + this.dataDoc.mass1_forcesUpdated = JSON.stringify([this.gravityForce(this.mass1), forceOfTension]); + this.dataDoc.mass1_componentForces = JSON.stringify([forceOfTension, gravityParallel, gravityPerpendicular]); + this._simReset++; + } + })} + radianEquivalent={true} + mode="Freeform" + labelWidth="5em" + /> + Rod length} + lowerBound={0} + dataDoc={this.dataDoc} + prop="pendulum_length" + step={1} + unit="m" + upperBound={400} + value={Math.round(this.pendulumLength)} + effect={action(value => { + if (this.simulationType == 'Pendulum') { + this.dataDoc.pendulum_angleStart = this.pendulumAngle; + this.dataDoc.pendulum_lengthStart = value; + this._simReset++; + } + })} + radianEquivalent={false} + mode="Freeform" + labelWidth="5em" + /> +
+ )} +
+ )} +
+ {this.simulationMode == 'Freeform' && ( + + + + + + + + + + {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && ( + + )}{' '} + {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && ( + + )}{' '} + {(!this.dataDoc.simulation_paused || this.simulationType == 'Inclined Plane' || this.simulationType == 'Circular Motion' || this.simulationType == 'Pulley') && ( + + )}{' '} + {this.dataDoc.simulation_paused && this.simulationType != 'Inclined Plane' && this.simulationType != 'Circular Motion' && this.simulationType != 'Pulley' && ( + + )}{' '} + + + + {(!this.dataDoc.simulation_paused || (this.simulationType != 'One Weight' && this.simulationType != 'Circular Motion')) && ( + + )}{' '} + {this.dataDoc.simulation_paused && (this.simulationType == 'One Weight' || this.simulationType == 'Circular Motion') && ( + + )}{' '} + {(!this.dataDoc.simulation_paused || this.simulationType != 'One Weight') && ( + + )}{' '} + {this.dataDoc.simulation_paused && this.simulationType == 'One Weight' && ( + + )}{' '} + + + + + + + + + + + + +
{this.simulationType == 'Pulley' ? 'Red Weight' : ''}XY
{ + // window.open( + // "https://www.khanacademy.org/science/physics/two-dimensional-motion" + // ); + // }} + > + Position + + <>{this.dataDoc.mass1_positionX} m + + { + this.dataDoc.mass1_xChange = value; + if (this.simulationType == 'Suspension') { + let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; + let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; + let deltaX1 = value + this.radius - x1rod; + let deltaX2 = x2rod - (value + this.radius); + let deltaY = this.getYPosFromDisplay(NumCast(this.dataDoc.mass1_positionY)) + this.radius; + let dir1T = Math.PI - Math.atan(deltaY / deltaX1); + let dir2T = Math.atan(deltaY / deltaX2); + let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); + dir1T = (dir1T * 180) / Math.PI; + dir2T = (dir2T * 180) / Math.PI; + const tensionForce1: IForce = { + description: 'Tension', + magnitude: tensionMag1, + directionInDegrees: dir1T, + }; + const tensionForce2: IForce = { + description: 'Tension', + magnitude: tensionMag2, + directionInDegrees: dir2T, + }; + const gravity = this.gravityForce(this.mass1); + this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); + } + }} + small={true} + mode="Freeform" + /> + {`${NumCast(this.dataDoc.mass1_positionY)} m`} + { + this.dataDoc.mass1_yChange = value; + if (this.simulationType == 'Suspension') { + let x1rod = (this.xMax + this.xMin) / 2 - this.radius - this.yMin - 200; + let x2rod = (this.xMax + this.xMin) / 2 + this.yMin + 200 + this.radius; + let deltaX1 = NumCast(this.dataDoc.mass1_positionX) + this.radius - x1rod; + let deltaX2 = x2rod - (NumCast(this.dataDoc.mass1_positionX) + this.radius); + let deltaY = this.getYPosFromDisplay(value) + this.radius; + let dir1T = Math.PI - Math.atan(deltaY / deltaX1); + let dir2T = Math.atan(deltaY / deltaX2); + let tensionMag2 = (this.mass1 * Math.abs(this.gravity)) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + let tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); + dir1T = (dir1T * 180) / Math.PI; + dir2T = (dir2T * 180) / Math.PI; + const tensionForce1: IForce = { + description: 'Tension', + magnitude: tensionMag1, + directionInDegrees: dir1T, + }; + const tensionForce2: IForce = { + description: 'Tension', + magnitude: tensionMag2, + directionInDegrees: dir2T, + }; + const gravity = this.gravityForce(this.mass1); + this.dataDoc.mass1_forcesUpdated = JSON.stringify([tensionForce1, tensionForce2, gravity]); + } + }} + small={true} + mode="Freeform" + /> +
{ + // window.open( + // "https://www.khanacademy.org/science/physics/two-dimensional-motion" + // ); + // }} + > + Velocity + {`${NumCast(this.dataDoc.mass1_velocityX)} m/s`} + { + this.dataDoc.mass1_velocityXstart = value; + this._simReset++; + })} + small={true} + mode="Freeform" + /> + + <>{this.dataDoc.mass1_velocityY} m/s + + { + this.dataDoc.mass1_velocityYstart = -value; + }} + small={true} + mode="Freeform" + /> +
{ + // window.open( + // "https://www.khanacademy.org/science/physics/two-dimensional-motion" + // ); + // }} + > + Acceleration + + <> + {this.dataDoc.mass1_accelerationX} m/s2 + + + <> + {this.dataDoc.mass1_accelerationY} m/s2 + +
+ Momentum + {Math.round(NumCast(this.dataDoc.mass1_velocityX) * this.mass1 * 10) / 10} kg*m/s{Math.round(NumCast(this.dataDoc.mass1_velocityY) * this.mass1 * 10) / 10} kg*m/s
+ )} + {this.simulationMode == 'Freeform' && this.simulationType == 'Pulley' && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Blue WeightXY
+ Position + {`${this.dataDoc.mass2_positionX} m`}{`${this.dataDoc.mass2_positionY} m`}
+ Velocity + {`${this.dataDoc.mass2_positionX} m/s`}{`${this.dataDoc.mass2_positionY} m/s`}
+ Acceleration + + <> + {this.dataDoc.mass2_accelerationX} m/s2 + + + <> + {this.dataDoc.mass2_accelerationY} m/s2 + +
+ Momentum + {Math.round(NumCast(this.dataDoc.mass2_velocityX) * this.mass1 * 10) / 10} kg*m/s{Math.round(NumCast(this.dataDoc.mass2_velocityY) * this.mass1 * 10) / 10} kg*m/s
+ )} +
+ {this.simulationType != 'Pendulum' && this.simulationType != 'Spring' && ( +
+

Kinematic Equations

+
    +
  • + Position: x1=x0+v0t+ + 1⁄ + 2at + 2 +
  • +
  • + Velocity: v1=v0+at +
  • +
  • Acceleration: a = F/m
  • +
+
+ )} + {this.simulationType == 'Spring' && ( +
+

Harmonic Motion Equations: Spring

+
    +
  • + Spring force: Fs=kd +
  • +
  • + Spring period: Ts=2π√m⁄ + k +
  • +
  • Equilibrium displacement for vertical spring: d = mg/k
  • +
  • + Elastic potential energy: Us=1⁄ + 2kd2 +
  • +
      +
    • Maximum when system is at maximum displacement, 0 when system is at 0 displacement
    • +
    +
  • + Translational kinetic energy: K=1⁄ + 2mv2 +
  • +
      +
    • Maximum when system is at maximum/minimum velocity (at 0 displacement), 0 when velocity is 0 (at maximum displacement)
    • +
    +
+
+ )} + {this.simulationType == 'Pendulum' && ( +
+

Harmonic Motion Equations: Pendulum

+
    +
  • + Pendulum period: Tp=2π√l⁄ + g +
  • +
+
+ )} +
+
+
+ + + + + + + + + +

+ {this.simulationType == 'Circular Motion' ? 'Z' : 'Y'} +

+

+ X +

+
+
+ ); + } +} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx new file mode 100644 index 000000000..d595a499e --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationInputField.tsx @@ -0,0 +1,179 @@ +import { TextField, InputAdornment } from '@mui/material'; +import { Doc } from '../../../../fields/Doc'; +import React = require('react'); +import TaskAltIcon from '@mui/icons-material/TaskAlt'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import { isNumber } from 'lodash'; +export interface IInputProps { + label?: JSX.Element; + lowerBound: number; + dataDoc: Doc; + prop: string; + step: number; + unit: string; + upperBound: number; + value: number | string | Array; + correctValue?: number; + showIcon?: boolean; + effect?: (val: number) => any; + radianEquivalent?: boolean; + small?: boolean; + mode?: string; + update?: boolean; + labelWidth?: string; +} + +interface IState { + tempValue: string | number | (string | number)[]; + tempRadianValue: number; + width: string; + margin: string; +} + +export default class InputField extends React.Component { + constructor(props: any) { + super(props); + this.state = { + tempValue: this.props.mode != 'Freeform' && !this.props.showIcon ? 0 : this.props.value, + tempRadianValue: this.props.mode != 'Freeform' && !this.props.showIcon ? 0 : (Number(this.props.value) * Math.PI) / 180, + width: this.props.small ? '6em' : '7em', + margin: this.props.small ? '0px' : '10px', + }; + } + + epsilon: number = 0.01; + + componentDidMount(): void { + this.setState({ tempValue: Number(this.props.value) }); + } + + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { + if (prevProps.value != this.props.value && isNumber(this.props.value) && !isNaN(this.props.value)) { + if (this.props.mode == 'Freeform') { + if (isNumber(this.state.tempValue) && Math.abs(this.state.tempValue - Number(this.props.value)) > 1) { + this.setState({ tempValue: Number(this.props.value) }); + } + } + } + if (prevProps.update != this.props.update) { + this.externalUpdate(); + } + } + + externalUpdate = () => { + this.setState({ tempValue: Number(this.props.value) }); + this.setState({ tempRadianValue: (Number(this.props.value) * Math.PI) / 180 }); + }; + + onChange = (event: React.ChangeEvent) => { + let value = event.target.value == '' ? 0 : Number(event.target.value); + if (value > this.props.upperBound) { + value = this.props.upperBound; + } else if (value < this.props.lowerBound) { + value = this.props.lowerBound; + } + if (this.props.prop != '') { + this.props.dataDoc[this.props.prop] = value; + } + this.setState({ tempValue: event.target.value == '' ? event.target.value : value }); + this.setState({ tempRadianValue: (value * Math.PI) / 180 }); + if (this.props.effect) { + this.props.effect(value); + } + }; + + onChangeRadianValue = (event: React.ChangeEvent) => { + let value = event.target.value === '' ? 0 : Number(event.target.value); + if (value > 2 * Math.PI) { + value = 2 * Math.PI; + } else if (value < 0) { + value = 0; + } + if (this.props.prop != '') { + this.props.dataDoc[this.props.prop] = (value * 180) / Math.PI; + } + this.setState({ tempValue: (value * 180) / Math.PI }); + this.setState({ tempRadianValue: value }); + if (this.props.effect) { + this.props.effect((value * 180) / Math.PI); + } + }; + + render() { + return ( +
+ {this.props.label && ( +
+ {this.props.label} +
+ )} + + {Math.abs(Number(this.props.value) - (this.props.correctValue ?? 0)) < this.epsilon && this.props.showIcon && } + {Math.abs(Number(this.props.value) - (this.props.correctValue ?? 0)) >= this.epsilon && this.props.showIcon && } + + ), + endAdornment: {this.props.unit}, + }} + /> + {this.props.radianEquivalent && ( +
+

≈

+
+ )} + {this.props.radianEquivalent && ( + rad, + }} + /> + )} +
+ ); + } +} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationQuestions.json b/src/client/views/nodes/PhysicsBox/PhysicsSimulationQuestions.json new file mode 100644 index 000000000..cc79f7aad --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationQuestions.json @@ -0,0 +1,161 @@ +{ + "inclinePlane": [ + { + "questionSetup": [ + "There is a 1kg weight on an inclined plane. The plane is at a ", + " angle from the ground. The system is in equilibrium (the net force on the weight is 0)." + ], + "variablesForQuestionSetup": ["theta - max 45"], + "question": "What are the magnitudes and directions of the forces acting on the weight?", + "answerParts": [ + "force of gravity", + "angle of gravity", + "normal force", + "angle of normal force", + "force of static friction", + "angle of static friction" + ], + "answerSolutionDescriptions": [ + "9.81", + "270", + "solve normal force magnitude from wedge angle", + "solve normal force angle from wedge angle", + "solve static force magnitude from wedge angle given equilibrium", + "solve static force angle from wedge angle given equilibrium" + ], + "goal": "noMovement", + "hints": [ + { + "description": "Direction of Force of Gravity", + "content": "The force of gravity acts in the negative y direction: 3π/2 rad." + }, + { + "description": "Direction of Normal Force", + "content": "The normal force acts in the direction perpendicular to the incline plane: π/2-θ rad, where θ is the angle of the incline plane." + }, + { + "description": "Direction of Force of Friction", + "content": "The force of friction acts in the direction along the incline plane: π-θ rad, where θ is the angle of the incline plane." + }, + { + "description": "Magnitude of Force of Gravity", + "content": "The magnitude of the force of gravity is approximately 9.81." + }, + { + "description": "Magnitude of Normal Force", + "content": "The magnitude of the normal force is equal to m*g*cos(θ), where θ is the angle of the incline plane." + }, + { + "description": "Net Force in Equilibrium", + "content": "For the system to be in equilibrium, the sum of the x components of all forces must equal 0, and the sum of the y components of all forces must equal 0." + }, + { + "description": "X Component of Normal Force", + "content": "The x component of the normal force is equal to m*g*cos(θ)*cos(π/2-θ), where θ is the angle of the incline plane." + }, + { + "description": "X Component of Force of Friction", + "content": "Since the net force in the x direction must be 0, we know the magnitude of the x component of the friction force is m*g*cos(θ)*cos(π/2-θ)." + }, + { + "description": "Y Component of Normal Force", + "content": "The y component of the normal force is equal to m*g*cos(θ)*sin(π/2-θ), where θ is the angle of the incline plane. The y component of gravity is equal to m*g" + }, + { + "description": "Y Component of Force of Friction", + "content": "Since the net force in the x direction must be 0, we know the magnitude of the y component of the friction force is m*g-m*g*cos(θ)*sin(π/2-θ)." + }, + { + "description": "Magnitude of Force of Friction", + "content": "Combining the x and y components of the friction force, we get the magnitude of the friction force is equal to sqrt((m*g*cos(θ)*cos(π/2-θ))^2 + (m*g-m*g*cos(θ)*sin(π/2-θ))^2)." + } + ] + }, + { + "questionSetup": [ + "There is a 1kg weight on an inclined plane. The plane is at a ", + " angle from the ground. The system is in equilibrium (the net force on the weight is 0)." + ], + "variablesForQuestionSetup": ["theta - max 45"], + "question": "What is the minimum coefficient of static friction?", + "answerParts": ["coefficient of static friction"], + "answerSolutionDescriptions": [ + "solve minimum static coefficient from wedge angle given equilibrium" + ], + "goal": "noMovement", + "hints": [ + { + "description": "Net Force in Equilibrium", + "content": "If the system is in equilibrium, the sum of the x components of all forces must equal 0. In this system, the normal force and force of static friction have non-zero x components." + }, + { + "description": "X Component of Normal Force", + "content": "The x component of the normal force is equal to m*g*cos(θ)*cos(π/2-θ), where θ is the angle of the incline plane." + }, + { + "description": "X Component of Force of Friction", + "content": "The x component of the force of static friction is equal to μ*m*g*cos(θ)*cos(π-θ), where θ is the angle of the incline plane." + }, + { + "description": "Equation to Solve for Minimum Coefficient of Static Friction", + "content": "Since the net force in the x direction must be 0, we can solve the equation 0=m*g*cos(θ)*cos(π/2-θ)+μ*m*g*cos(θ)*cos(π-θ) for μ to find the minimum coefficient of static friction such that the system stays in equilibrium." + } + ] + }, + { + "questionSetup": [ + "There is a 1kg weight on an inclined plane. The coefficient of static friction is ", + ". The system is in equilibrium (the net force on the weight is 0)." + ], + "variablesForQuestionSetup": ["coefficient of static friction"], + "question": "What is the maximum angle of the plane from the ground?", + "answerParts": ["wedge angle"], + "answerSolutionDescriptions": [ + "solve maximum wedge angle from coefficient of static friction given equilibrium" + ], + "goal": "noMovement", + "hints": [ + { + "description": "Net Force in Equilibrium", + "content": "If the system is in equilibrium, the sum of the x components of all forces must equal 0. In this system, the normal force and force of static friction have non-zero x components." + }, + { + "description": "X Component of Normal Force", + "content": "The x component of the normal force is equal to m*g*cos(θ)*cos(π/2-θ), where θ is the angle of the incline plane." + }, + { + "description": "X Component of Force of Friction", + "content": "The x component of the force of static friction is equal to μ*m*g*cos(θ)*cos(π-θ), where θ is the angle of the incline plane." + }, + { + "description": "Equation to Solve for Maximum Wedge Angle", + "content": "Since the net force in the x direction must be 0, we can solve the equation 0=m*g*cos(θ)*cos(π/2-θ)+μ*m*g*cos(θ)*cos(π-θ) for θ to find the maximum wedge angle such that the system stays in equilibrium." + }, + { + "description": "Simplifying Equation to Solve for Maximum Wedge Angle", + "content": "Simplifying 0=m*g*cos(θ)*cos(π/2-θ)+μ*m*g*cos(θ)*cos(π-θ), we get cos(π/2-θ)=-μ*cos(π-θ)." + }, + { + "description": "Simplifying Equation to Solve for Maximum Wedge Angle", + "content": "The cosine subtraction formula states that cos(A-B)=cos(A)*cos(B)+sin(A)sin(B)." + }, + { + "description": "Simplifying Equation to Solve for Maximum Wedge Angle", + "content": "Applying the cosine subtraction formula to cos(π/2-θ)=-μ*cos(π-θ), we get cos(π/2)*cos(θ)+sin(π/2)*sin(θ)=-μ*(cos(π)cos(θ)+sin(π)sin(θ))." + }, + { + "description": "Simplifying Equation to Solve for Maximum Wedge Angle", + "content": "Simplifying cos(π/2)*cos(θ)-sin(π/2)*sin(θ)=-μ*(cos(π)cos(θ)-sin(π)sin(θ)), we get -sin(θ)=-μ*(-cos(θ))." + }, + { + "description": "Simplifying Equation to Solve for Maximum Wedge Angle", + "content": "Simplifying -sin(θ)=-μ*(-cos(θ)), we get tan(θ)=-μ." + }, + { + "description": "Simplifying Equation to Solve for Maximum Wedge Angle", + "content": "Solving for θ, we get θ = atan(μ)." + } + ] + } + ] +} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationTutorial.json b/src/client/views/nodes/PhysicsBox/PhysicsSimulationTutorial.json new file mode 100644 index 000000000..3015deaa4 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationTutorial.json @@ -0,0 +1,600 @@ +{ + "freeWeight": { + "question": "A 1kg weight is at rest on the ground. What are the magnitude and directions of the forces acting on the weight?", + "steps": [ + { + "description": "Forces", + "content": "There are two forces acting on the weight: the force of gravity and the normal force.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Normal Force", + "magnitude": 9.81, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Force of Gravity", + "content": "The force of gravity acts in the negative y direction: 3π/2 rad. It has magnitude equal to m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Normal Force", + "content": "The normal force acts in the positive y direction: π/2 rad. It has magnitude equal to m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Normal Force", + "magnitude": 9.81, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "All Forces", + "content": "Combining all of the forces, we get the following free body diagram.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Normal Force", + "magnitude": 9.81, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + } + ] + }, + "pendulum": { + "question": "A 1kg weight on a 300m rod of negligible mass is released from an angle 30 degrees offset from equilibrium. What are the magnitude and directions of the forces acting on the weight immediately after release? (Ignore air resistance)", + "steps": [ + { + "description": "Forces", + "content": "There are two force acting on the weight: the force of gravity and the force of tension.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Tension", + "magnitude": 8.5, + "directionInDegrees": 60, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Force of Gravity", + "content": "The force of gravity acts in the negative y direction: 3π/2 rad. It has magnitude equal to m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Tension", + "content": "The force of tension acts along the direction of the rod. The rod is 30 degrees offset from equilibrium, so the direction along the rod is 90-30=60 degrees. The tension force has two components—the component creating the centripetal force and the component canceling out the parallel component of gravity. The weight has just been released, so it has velocity 0, meaning the centripetal force is 0. Thus, the tension force only acts to cancel out the parallel component of gravity. Thus, the magnitude of tension is m*g*sin(60°)", + "forces": [ + { + "description": "Tension", + "magnitude": 8.5, + "directionInDegrees": 60, + "component": false + }, + { + "description": "Gravity - Parallel Component", + "magnitude": 8.5, + "directionInDegrees": 240, + "component": true + } + ], + "showMagnitude": true + }, + { + "description": "All Forces", + "content": "Combining all of the forces, we get the following free body diagram.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Tension", + "magnitude": 8.5, + "directionInDegrees": 60, + "component": false + } + ], + "showMagnitude": true + } + ] + }, + "inclinePlane": { + "question": "There is a 1kg weight on an inclined plane. The plane is at an angle θ from the ground, and has a coefficient of static friction μ. The system is in equilibrium (the net force on the weight is 0). What are the magnitudes and directions of the forces acting on the weight?", + "steps": [ + { + "description": "Forces", + "content": "There are three forces acting on the weight: the force of gravity, the normal force, and the force of static friction.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Normal Force", + "magnitude": 8.817, + "directionInDegrees": 64, + "component": false + }, + { + "description": "Friction Force", + "magnitude": 4.3, + "directionInDegrees": 154, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Force of Gravity", + "content": "The force of gravity acts in the negative y direction: 3π/2 rad. It has magnitude equal to m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Normal Force", + "content": "The normal force acts in the direction perpendicular to the incline plane: π/2-θ rad, where θ is the angle of the incline plane. The magnitude of the normal force is equal to m*g*cos(θ).", + "forces": [ + { + "description": "Normal Force", + "magnitude": 8.817, + "directionInDegrees": 64, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Force of Static Friction", + "content": "The force of static friction acts in the direction along the incline plane: π-θ rad, where θ is the angle of the incline plane. We can use the knowledge that the system is in equilibrium to solve for the magnitude of the force of static friction.", + "forces": [ + { + "description": "Friction Force", + "magnitude": 4.3, + "directionInDegrees": 154, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Net X Force in Equilibrium", + "content": "For the system to be in equilibrium, the sum of the x components of all forces must equal 0. The x component of the normal force is equal to m*g*cos(θ)*cos(π/2-θ), where θ is the angle of the incline plane. The x component of gravity is equal to 0. Since the net force in the x direction must be 0, we know the magnitude of the x component of the friction force is m*g*cos(θ)*cos(π/2-θ).", + "forces": [ + { + "description": "Normal Force - X Component", + "magnitude": 3.87, + "directionInDegrees": 0, + "component": true + }, + { + "description": "Friction Force - X Component", + "magnitude": 3.87, + "directionInDegrees": 180, + "component": true + } + ], + "showMagnitude": true + }, + { + "description": "Net Y Force Normal Force", + "content": "For the system to be in equilibrium, the sum of the y components of all forces must equal 0. The y component of the normal force is equal to m*g*cos(θ)*sin(π/2-θ), where θ is the angle of the incline plane. The y component of gravity is equal to m*g. Since the net force in the x direction must be 0, we know the magnitude of the y component of the friction force is m*g-m*g*cos(θ)*sin(π/2-θ).", + "forces": [ + { + "description": "Normal Force - Y Component ", + "magnitude": 7.92, + "directionInDegrees": 90, + "component": true + }, + { + "description": "Gravity - Y Component ", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": true + }, + { + "description": "Friction Force - Y Component ", + "magnitude": 1.89, + "directionInDegrees": 90, + "component": true + } + ], + "showMagnitude": true + }, + { + "description": "Magnitude of Force of Friction", + "content": "Combining the x and y components of the friction force, we get the magnitude of the friction force is equal to sqrt((m*g*cos(θ)*cos(π/2-θ))^2 + (m*g*cos(θ)*sin(π/2-θ)-m*g)^2).", + "forces": [ + { + "description": "Friction Force", + "magnitude": 4.3, + "directionInDegrees": 154, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "All Forces", + "content": "Combining all of the forces, we get the following free body diagram.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Normal Force", + "magnitude": 8.817, + "directionInDegrees": 64, + "component": false + }, + { + "description": "Friction Force", + "magnitude": 4.3, + "directionInDegrees": 154, + "component": false + } + ], + "showMagnitude": true + } + ] + }, + "spring": { + "question": "A 1kg weight is on a spring of negligible mass with rest length 200m and spring constant 0.5. What is the equilibrium spring length?", + "steps": [ + { + "description": "Forces", + "content": "We can start by solving for the forces acting on the weight at any given point in time. There are two forces potentially acting on the weight: the force of gravity and the spring force. In equilibrium, these forces will be perfectly balanced.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Spring Force", + "magnitude": 9.81, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Force of Gravity", + "content": "The force of gravity acts in the negative y direction: 3π/2 rad. It has magnitude equal to m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Spring Force", + "content": "The spring force acts in the negative y direction (3π/2 rad) if the spring is compressed. The spring force acts in the positive y direction (π/2 rad) if the spring is extended. Because the forces are perfectly balanced and gravity acts in the negative y direction, the spring force must act in the positive y direction and have the same magnitude as the force og gravity, m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Spring Force", + "magnitude": 9.81, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Spring Force", + "content": "We can use the spring force equation, Fs=kd to solve for the displacement such that Fs=mg. Setting them equal, we get mg=kd. Plugging in for the known values of m,g, and k, we get 1*9.81=0.5d. Solving for d, we get d=19.62 as the equilibrium starting displacement", + "forces": [ + { + "description": "Spring Force", + "magnitude": 9.81, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + } + ] + }, + "circular": { + "question": "A 1kg weight is attached to a 100m rod of negligible mass. The weight is undergoing uniform circular motion with tangential velocity 40 m/s. What are the magnitude and directions of the forces acting on the weight? (Ignore air resistance)", + "steps": [ + { + "description": "Forces", + "content": "There is one force acting on the weight: the centripetal force.", + "forces": [ + { + "description": "Centripetal Force", + "magnitude": 16, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Centripetal Force", + "content": "The centripetal force is always directed toward the center of the circle. The formula for solving for the magnitude of centripetal force for an object undergoing uniform circular motion is Fc=mv^2 / r. Plugging in for known values, we get Fc=1*(40^2)/100. Solving for this, we get Fc=16", + "forces": [ + { + "description": "Centripetal Force", + "magnitude": 16, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + } + ] + }, + "pulley": { + "question": "A 1kg red weight is attached to a simple pulley with a rope of negligible mass. A 1.5kg blue weight is attached to the other end of the simple pulley. What are the forces acting on the red weight?", + "steps": [ + { + "description": "Forces", + "content": "There are two force acting on the red weight: the force of gravity and the force of tension.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Tension", + "magnitude": 11.77, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Gravity", + "content": "The force of gravity acts in the negative y direction: 3π/2 rad. It has magnitude equal to m*g. We can approximate g as 9.81.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "Tension", + "content": "The force of tension acts in the positive y direction: π/2 rad. We know that the acceleration in a simple pulley system is (mass 2 - mass 1) * acceleration due to gravity / (mass 1 + mass 2) = (1.5-1) * 9.81 / (1.5+1) = 1.962 m/s^2. Because the acceleration is caused by the force of gravity and force of tension, we can solve for the force of tension acting on the weight as mass 1 * (a + acceleration due to gravity) = 1 * (1.962+9.81) = 11.77.", + "forces": [ + { + "description": "Tension", + "magnitude": 11.77, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "All Forces", + "content": "Combining all of the forces, we get the following free body diagram.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Tension", + "magnitude": 11.77, + "directionInDegrees": 90, + "component": false + } + ], + "showMagnitude": true + } + ] + }, + "suspension": { + "question": "A 1kg weight is attached to two rods hanging from 45° angles from the ceiling. The system is in equilibrium, i.e. the weight does not move. What are the magnitudes and directions of the forces acting on the weight?", + "steps": [ + { + "description": "Forces", + "content": "There are three force acting on the red weight: the force of gravity, the force of tension from the left rod, and the force of tension from the right rod.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Left Tension", + "magnitude": 6.94, + "directionInDegrees": 135, + "component": false + }, + { + "description": "Right Tension", + "magnitude": 6.94, + "directionInDegrees": 45, + "component": false + } + ], + "showMagnitude": false + }, + { + "description": "Force X Components", + "content": "There are two forces with x components to consider: the tension from the left rod and the tension from the right rod. These must cancel each other out so that the net x force is 0.", + "forces": [ + { + "description": "Left Tension X Component", + "magnitude": 4.907, + "directionInDegrees": 180, + "component": true + }, + { + "description": "Right Tension X Component", + "magnitude": 4.907, + "directionInDegrees": 0, + "component": true + } + ], + "showMagnitude": false + }, { + "description": "Force Y Components", + "content": "There are three forces with y components to consider: the tension from the left rod, the tension from the right rod, and the force of gravity.", + "forces": [ + { + "description": "Left Tension Y Component", + "magnitude": 4.907, + "directionInDegrees": 90, + "component": true + }, + { + "description": "Right Tension Y Component", + "magnitude": 4.907, + "directionInDegrees": 90, + "component": true + }, + { + "description": "Gravity Y Component", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": true + } + ], + "showMagnitude": false + }, { + "description": "Force Y Components", + "content": "The y components of forces must cancel each other out so that the net y force is 0. Thus, gravity = left tension y component + right tension y component. Because the x components of tension are the same and the angles of each rod are the same, the y components must be the same. Thus, the y component for each force of tension must be 9.81/2.", + "forces": [ + { + "description": "Left Tension Y Component", + "magnitude": 4.907, + "directionInDegrees": 90, + "component": true + }, + { + "description": "Right Tension Y Component", + "magnitude": 4.907, + "directionInDegrees": 90, + "component": true + }, + { + "description": "Gravity Y Component", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": true + } + ], + "showMagnitude": true + }, { + "description": "Tension", + "content": "Now that we know the y component of tension for each rod is 4.907, we can solve for the full force of tension as 4.907 = T * sin(45°) -> T = 6.94.", + "forces": [ + { + "description": "Left Tension", + "magnitude": 6.94, + "directionInDegrees": 135, + "component": false + }, + { + "description": "Right Tension", + "magnitude": 6.94, + "directionInDegrees": 45, + "component": false + } + ], + "showMagnitude": true + }, + { + "description": "All Forces", + "content": "Combining all of the forces, we get the following free body diagram.", + "forces": [ + { + "description": "Gravity", + "magnitude": 9.81, + "directionInDegrees": 270, + "component": false + }, + { + "description": "Left Tension", + "magnitude": 6.94, + "directionInDegrees": 135, + "component": false + }, + { + "description": "Right Tension", + "magnitude": 6.94, + "directionInDegrees": 45, + "component": false + } + ], + "showMagnitude": true + } + ] + } +} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx new file mode 100644 index 000000000..8cc1d0fbf --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWall.tsx @@ -0,0 +1,34 @@ +import React = require('react'); + +export interface Force { + magnitude: number; + directionInDegrees: number; +} +export interface IWallProps { + length: number; + xPos: number; + yPos: number; + angleInDegrees: number; +} + +export default class Wall extends React.Component { + + constructor(props: any) { + super(props) + } + + wallStyle = { + width: this.props.angleInDegrees == 0 ? this.props.length + "%" : "5px", + height: this.props.angleInDegrees == 0 ? "5px" : this.props.length + "%", + position: "absolute" as "absolute", + left: this.props.xPos + "%", + top: this.props.yPos + "%", + backgroundColor: "#6c7b8b", + margin: 0, + padding: 0, + }; + + render () { + return (
); + } +}; diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx new file mode 100644 index 000000000..2165c8ba9 --- /dev/null +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationWeight.tsx @@ -0,0 +1,990 @@ +import { computed, IReactionDisposer, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import './PhysicsSimulationBox.scss'; +import React = require('react'); + +interface IWallProps { + length: number; + xPos: number; + yPos: number; + angleInDegrees: number; +} +interface IForce { + description: string; + magnitude: number; + directionInDegrees: number; +} +export interface IWeightProps { + pause: () => void; + panelWidth: () => number; + panelHeight: () => number; + resetRequest: () => number; + circularMotionRadius: number; + coefficientOfKineticFriction: number; + color: string; + componentForces: () => IForce[]; + setComponentForces: (x: IForce[]) => {}; + displayXVelocity: number; + displayYVelocity: number; + elasticCollisions: boolean; + gravity: number; + mass: number; + simulationMode: string; + noMovement: boolean; + paused: boolean; + pendulumAngle: number; + pendulumLength: number; + radius: number; + showAcceleration: boolean; + showComponentForces: boolean; + showForceMagnitudes: boolean; + showForces: boolean; + showVelocity: boolean; + simulationSpeed: number; + simulationType: string; + springConstant: number; + springRestLength: number; + springStartLength: number; + startForces: () => IForce[]; + startPendulumAngle: number; + startPendulumLength: number; + startPosX: number; + startPosY: number; + startVelX: number; + startVelY: number; + timestepSize: number; + updateMassPosX: number; + updateMassPosY: number; + forcesUpdated: () => IForce[]; + setForcesUpdated: (x: IForce[]) => {}; + setPosition: (x: number | undefined, y: number | undefined) => void; + setVelocity: (x: number | undefined, y: number | undefined) => void; + setAcceleration: (x: number, y: number) => void; + setPendulumAngle: (ang: number | undefined, length: number | undefined) => void; + setSpringLength: (length: number) => void; + wallPositions: IWallProps[]; + wedgeHeight: number; + wedgeWidth: number; + xMax: number; + xMin: number; + yMax: number; + yMin: number; +} + +interface IState { + angleLabel: number; + clickPositionX: number; + clickPositionY: number; + coordinates: string; + dragging: boolean; + kineticFriction: boolean; + maxPosYConservation: number; + timer: number; + updatedStartPosX: any; + updatedStartPosY: any; + xPosition: number; + xVelocity: number; + yPosition: number; + yVelocity: number; + xAccel: number; + yAccel: number; +} +@observer +export default class Weight extends React.Component { + constructor(props: any) { + super(props); + this.state = { + angleLabel: 0, + clickPositionX: 0, + clickPositionY: 0, + coordinates: '', + dragging: false, + kineticFriction: false, + maxPosYConservation: 0, + timer: 0, + updatedStartPosX: this.props.startPosX, + updatedStartPosY: this.props.startPosY, + xPosition: this.props.startPosX, + xVelocity: this.props.startVelX, + yPosition: this.props.startPosY, + yVelocity: this.props.startVelY, + xAccel: 0, + yAccel: 0, + }; + } + + _timer: NodeJS.Timeout | undefined; + _resetDisposer: IReactionDisposer | undefined; + + componentWillUnmount() { + this._timer && clearTimeout(this._timer); + this._resetDisposer?.(); + } + componentWillUpdate(nextProps: Readonly, nextState: Readonly, nextContext: any): void { + if (nextProps.paused) { + this._timer && clearTimeout(this._timer); + this._timer = undefined; + } else if (this.props.paused) { + this._timer && clearTimeout(this._timer); + this._timer = setInterval(() => this.setState({ timer: this.state.timer + 1 }), 50); + } + } + + // Constants + @computed get draggable() { + return !['Inclined Plane', 'Pendulum'].includes(this.props.simulationType) && this.props.simulationMode === 'Freeform'; + } + @computed get panelHeight() { + return Math.max(800, this.props.panelHeight()) + 'px'; + } + @computed get panelWidth() { + return Math.max(1000, this.props.panelWidth()) + 'px'; + } + + @computed get walls() { + return ['One Weight', 'Inclined Plane'].includes(this.props.simulationType) ? this.props.wallPositions : []; + } + epsilon = 0.0001; + labelBackgroundColor = `rgba(255,255,255,0.5)`; + + // Variables + weightStyle = { + alignItems: 'center', + backgroundColor: this.props.color, + borderColor: 'black', + borderRadius: 50 + '%', + borderStyle: 'solid', + display: 'flex', + height: 2 * this.props.radius + 'px', + justifyContent: 'center', + left: this.props.startPosX + 'px', + position: 'absolute' as 'absolute', + top: this.props.startPosY + 'px', + touchAction: 'none', + width: 2 * this.props.radius + 'px', + zIndex: 5, + }; + + // Helper function to go between display and real values + getDisplayYPos = (yPos: number) => this.props.yMax - yPos - 2 * this.props.radius + 5; + gravityForce = (): IForce => ({ + description: 'Gravity', + magnitude: this.props.mass * this.props.gravity, + directionInDegrees: 270, + }); + // Update display values when simulation updates + setDisplayValues = (xPos: number = this.state.xPosition, yPos: number = this.state.yPosition, xVel: number = this.state.xVelocity, yVel: number = this.state.yVelocity) => { + this.props.setPosition(xPos, this.getDisplayYPos(yPos)); + this.props.setVelocity(xVel, yVel); + const xAccel = Math.round(this.getNewAccelerationX(this.props.forcesUpdated()) * 100) / 100; + const yAccel = (-1 * Math.round(this.getNewAccelerationY(this.props.forcesUpdated()) * 100)) / 100; + this.props.setAcceleration(xAccel, yAccel); + this.setState({ xAccel, yAccel }); + }; + componentDidMount() { + this._resetDisposer = reaction(() => this.props.resetRequest(), this.resetEverything); + } + componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { + if (prevProps.simulationType != this.props.simulationType) { + this.setState({ xVelocity: this.props.startVelX, yVelocity: this.props.startVelY }); + this.setDisplayValues(); + } + + // Change pendulum angle from input field + if (prevProps.startPendulumAngle != this.props.startPendulumAngle || prevProps.startPendulumLength !== this.props.startPendulumLength) { + const length = this.props.startPendulumLength; + const x = length * Math.cos(((90 - this.props.startPendulumAngle) * Math.PI) / 180); + const y = length * Math.sin(((90 - this.props.startPendulumAngle) * Math.PI) / 180); + const xPosition = this.props.xMax / 2 - x - this.props.radius; + const yPosition = y - this.props.radius - 5; + this.setState({ xPosition, yPosition, updatedStartPosX: xPosition, updatedStartPosY: yPosition }); + this.props.setPendulumAngle(this.props.startPendulumAngle, this.props.startPendulumLength); + } + + // When display values updated by user, update real value + if (prevProps.updateMassPosX !== this.props.updateMassPosX) { + const x = Math.min(Math.max(0, this.props.updateMassPosX), this.props.xMax - 2 * this.props.radius); + this.setState({ updatedStartPosX: x, xPosition: x }); + this.props.setPosition(x, undefined); + } + if (prevProps.updateMassPosY != this.props.updateMassPosY) { + const y = Math.min(Math.max(0, this.props.updateMassPosY), this.props.yMax - 2 * this.props.radius); + const coordinatePosition = this.getDisplayYPos(y); + this.setState({ yPosition: coordinatePosition, updatedStartPosY: coordinatePosition }); + this.props.setPosition(undefined, this.getDisplayYPos(y)); + + if (this.props.displayXVelocity != this.state.xVelocity) { + this.setState({ xVelocity: this.props.displayXVelocity }); + this.props.setVelocity(this.props.displayXVelocity, undefined); + } + + if (this.props.displayYVelocity != -this.state.yVelocity) { + this.setState({ yVelocity: -this.props.displayYVelocity }); + this.props.setVelocity(undefined, this.props.displayYVelocity); + } + } + + // Make sure weight doesn't go above max height + if ((prevState.updatedStartPosY != this.state.updatedStartPosY || prevProps.startVelY != this.props.startVelY) && !isNaN(this.state.updatedStartPosY) && !isNaN(this.props.startVelY)) { + if (this.props.simulationType == 'One Weight') { + let maxYPos = this.state.updatedStartPosY; + if (this.props.startVelY != 0) { + maxYPos -= (this.props.startVelY * this.props.startVelY) / (2 * this.props.gravity); + } + if (maxYPos < 0) maxYPos = 0; + + this.setState({ maxPosYConservation: maxYPos }); + } + } + + // Check for collisions and update + if (!this.props.paused && !this.props.noMovement && prevState.timer != this.state.timer) { + let collisions = false; + if (this.props.simulationType == 'One Weight' || this.props.simulationType == 'Inclined Plane') { + const collisionsWithGround = this.checkForCollisionsWithGround(); + const collisionsWithWalls = this.checkForCollisionsWithWall(); + collisions = collisionsWithGround || collisionsWithWalls; + } + if (this.props.simulationType == 'Pulley') { + if (this.state.yPosition <= this.props.yMin + 100 || this.state.yPosition >= this.props.yMax - 100) { + collisions = true; + } + } + if (!collisions) this.update(); + + this.setDisplayValues(); + } + + // Convert from static to kinetic friction if/when weight slips on inclined plane + if (prevState.xVelocity != this.state.xVelocity) { + if (this.props.simulationType == 'Inclined Plane' && Math.abs(this.state.xVelocity) > 0.1 && this.props.simulationMode != 'Review' && !this.state.kineticFriction) { + this.setState({ kineticFriction: true }); + const normalForce: IForce = { + description: 'Normal Force', + magnitude: this.props.mass * this.props.gravity * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), + directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, + }; + const frictionForce: IForce = { + description: 'Kinetic Friction Force', + magnitude: this.props.mass * this.props.coefficientOfKineticFriction * this.props.gravity * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), + directionInDegrees: 180 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, + }; + // reduce magnitude of friction force if necessary such that block cannot slide up plane + // prettier-ignore + const yForce = - this.props.gravity + + normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + + frictionForce.magnitude * Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); + if (yForce > 0) { + frictionForce.magnitude = (-normalForce.magnitude * Math.sin((normalForce.directionInDegrees * Math.PI) / 180) + this.props.gravity) / Math.sin((frictionForce.directionInDegrees * Math.PI) / 180); + } + + const normalForceComponent: IForce = { + description: 'Normal Force', + magnitude: this.props.mass * this.props.gravity * Math.cos(Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), + directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, + }; + const gravityParallel: IForce = { + description: 'Gravity Parallel Component', + magnitude: this.props.mass * this.props.gravity * Math.sin(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), + directionInDegrees: 180 - 90 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI + 180, + }; + const gravityPerpendicular: IForce = { + description: 'Gravity Perpendicular Component', + magnitude: this.props.mass * this.props.gravity * Math.cos(Math.PI / 2 - Math.atan(this.props.wedgeHeight / this.props.wedgeWidth)), + directionInDegrees: 360 - (Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI, + }; + const kineticFriction = this.props.coefficientOfKineticFriction != 0 ? [frictionForce] : []; + this.props.setForcesUpdated([this.gravityForce(), normalForce, ...kineticFriction]); + this.props.setComponentForces([normalForceComponent, gravityParallel, gravityPerpendicular, ...kineticFriction]); + } + } + + // Update x position when start pos x changes + if (prevProps.startPosX != this.props.startPosX) { + if (this.props.paused && !isNaN(this.props.startPosX)) { + this.setState({ xPosition: this.props.startPosX, updatedStartPosX: this.props.startPosX }); + this.props.setPosition(this.props.startPosX, undefined); + } + } + + // Update y position when start pos y changes TODO debug + if (prevProps.startPosY != this.props.startPosY) { + if (this.props.paused && !isNaN(this.props.startPosY)) { + this.setState({ yPosition: this.props.startPosY, updatedStartPosY: this.props.startPosY ?? 0 }); + this.props.setPosition(undefined, this.getDisplayYPos(this.props.startPosY)); + } + } + + // Update wedge coordinates + if (!this.state.coordinates || this.props.yMax !== prevProps.yMax || prevProps.wedgeWidth != this.props.wedgeWidth || prevProps.wedgeHeight != this.props.wedgeHeight) { + const left = this.props.xMax * 0.25; + const coordinatePair1 = Math.round(left) + ',' + this.props.yMax + ' '; + const coordinatePair2 = Math.round(left + this.props.wedgeWidth) + ',' + this.props.yMax + ' '; + const coordinatePair3 = Math.round(left) + ',' + (this.props.yMax - this.props.wedgeHeight); + this.setState({ coordinates: coordinatePair1 + coordinatePair2 + coordinatePair3 }); + } + + if (this.state.xPosition != prevState.xPosition || this.state.yPosition != prevState.yPosition) { + this.weightStyle = { + alignItems: 'center', + backgroundColor: this.props.color, + borderColor: 'black', + borderRadius: 50 + '%', + borderStyle: 'solid', + display: 'flex', + height: 2 * this.props.radius + 'px', + justifyContent: 'center', + left: this.state.xPosition + 'px', + position: 'absolute' as 'absolute', + top: this.state.yPosition + 'px', + touchAction: 'none', + width: 2 * this.props.radius + 'px', + zIndex: 5, + }; + } + } + + // Reset simulation on reset button click + resetEverything = () => { + this.setState({ + kineticFriction: false, + xPosition: this.state.updatedStartPosX, + yPosition: this.state.updatedStartPosY, + xVelocity: this.props.startVelX, + yVelocity: this.props.startVelY, + angleLabel: Math.round(this.props.pendulumAngle * 100) / 100, + }); + this.props.setPendulumAngle(this.props.startPendulumAngle, undefined); + this.props.setForcesUpdated(this.props.startForces()); + this.props.setPosition(this.state.updatedStartPosX, this.state.updatedStartPosY); + this.props.setVelocity(this.props.startVelX, this.props.startVelY); + this.props.setAcceleration(0, 0); + setTimeout(() => this.setState({ timer: this.state.timer + 1 })); + }; + + // Compute x acceleration from forces, F=ma + getNewAccelerationX = (forceList: IForce[]) => { + // prettier-ignore + return forceList.reduce((newXacc, force) => + newXacc + (force.magnitude * Math.cos((force.directionInDegrees * Math.PI) / 180)) / this.props.mass, 0); + }; + + // Compute y acceleration from forces, F=ma + getNewAccelerationY = (forceList: IForce[]) => { + // prettier-ignore + return forceList.reduce((newYacc, force) => + newYacc + (-1 * (force.magnitude * Math.sin((force.directionInDegrees * Math.PI) / 180))) / this.props.mass, 0); + }; + + // Compute uniform circular motion forces given x, y positions + getNewCircularMotionForces = (xPos: number, yPos: number): IForce[] => { + const deltaX = (this.props.xMin + this.props.xMax) / 2 - (xPos + this.props.radius); + const deltaY = yPos + this.props.radius - (this.props.yMin + this.props.yMax) / 2; + return [ + { + description: 'Centripetal Force', + magnitude: (this.props.startVelX ** 2 * this.props.mass) / this.props.circularMotionRadius, + directionInDegrees: (Math.atan2(deltaY, deltaX) * 180) / Math.PI, + }, + ]; + }; + + // Compute spring forces given y position + getNewSpringForces = (yPos: number): IForce[] => { + const yPosPlus = yPos - this.props.springRestLength > 0; + const yPosMinus = yPos - this.props.springRestLength < 0; + return [ + this.gravityForce(), + { + description: 'Spring Force', + magnitude: this.props.springConstant * (yPos - this.props.springRestLength) * (yPosPlus ? 1 : yPosMinus ? -1 : 0), + directionInDegrees: yPosPlus ? 90 : 270, + }, + ]; + }; + + // Compute pendulum forces given position, velocity + getNewPendulumForces = (xPos: number, yPos: number, xVel: number, yVel: number): IForce[] => { + const x = this.props.xMax / 2 - xPos - this.props.radius; + const y = yPos + this.props.radius + 5; + const angle = (ang => (ang < 0 ? ang + 180 : ang))((Math.atan(y / x) * 180) / Math.PI); + + let oppositeAngle = 90 - angle; + if (oppositeAngle < 0) { + oppositeAngle = 90 - (180 - angle); + } + + const pendulumLength = Math.sqrt(x * x + y * y); + this.props.setPendulumAngle(oppositeAngle, undefined); + + const mag = this.props.mass * this.props.gravity * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength; + + return [ + this.gravityForce(), + { + description: 'Tension', + magnitude: mag, + directionInDegrees: angle, + }, + ]; + }; + + // Check for collisions in x direction + checkForCollisionsWithWall = () => { + let collision = false; + if (this.state.xVelocity !== 0) { + this.walls + .filter(wall => wall.angleInDegrees === 90) + .forEach(wall => { + const wallX = (wall.xPos / 100) * this.props.panelWidth(); + const minX = this.state.xPosition < wallX && wall.xPos < 0.35; + const maxX = this.state.xPosition + 2 * this.props.radius >= wallX && wall.xPos > 0.35; + if (minX || maxX) { + this.setState({ + xPosition: minX ? wallX + 0.01 : wallX - 2 * this.props.radius - 0.01, + xVelocity: this.props.elasticCollisions ? -this.state.xVelocity : 0, + }); + collision = true; + } + }); + } + return collision; + }; + + // Check for collisions in y direction + checkForCollisionsWithGround = () => { + let collision = false; + const minY = this.state.yPosition; + const maxY = this.state.yPosition + 2 * this.props.radius; + if (this.state.yVelocity > 0) { + this.walls.forEach(wall => { + if (wall.angleInDegrees == 0 && wall.yPos > 0.4) { + const groundY = (wall.yPos / 100) * this.props.panelHeight(); + const gravity = this.gravityForce(); + if (maxY > groundY) { + this.setState({ yPosition: groundY - 2 * this.props.radius - 0.01 }); + if (this.props.elasticCollisions) { + this.setState({ yVelocity: -this.state.yVelocity }); + } else { + this.setState({ yVelocity: 0 }); + const normalForce: IForce = { + description: 'Normal force', + magnitude: gravity.magnitude, + directionInDegrees: -gravity.directionInDegrees, + }; + this.props.setForcesUpdated([gravity, normalForce]); + if (this.props.simulationType === 'Inclined Plane') { + this.props.setComponentForces([gravity, normalForce]); + } + } + collision = true; + } + } + }); + } + if (this.state.yVelocity < 0) { + this.walls.forEach(wall => { + if (wall.angleInDegrees == 0 && wall.yPos < 0.4) { + const groundY = (wall.yPos / 100) * this.props.panelHeight(); + if (minY < groundY) { + this.setState({ + yPosition: groundY + 0.01, + yVelocity: this.props.elasticCollisions ? -this.state.yVelocity : 0, + }); + collision = true; + } + } + }); + } + return collision; + }; + + // Called at each RK4 step + evaluate = (currentXPos: number, currentYPos: number, currentXVel: number, currentYVel: number, currdeltaXPos: number, currdeltaYPos: number, currdeltaXVel: number, currdeltaYVel: number, dt: number) => { + const xPos = currentXPos + currdeltaXPos * dt; + const yPos = currentYPos + currdeltaYPos * dt; + const xVel = currentXVel + currdeltaXVel * dt; + const yVel = currentYVel + currdeltaYVel * dt; + const forces = this.getForces(xPos, yPos, xVel, yVel); + return { + xPos, + yPos, + xVel, + yVel, + deltaXPos: xVel, + deltaYPos: yVel, + deltaXVel: this.getNewAccelerationX(forces), + deltaYVel: this.getNewAccelerationY(forces), + }; + }; + + getForces = (xPos: number, yPos: number, xVel: number, yVel: number) => { + // prettier-ignore + switch (this.props.simulationType) { + case 'Pendulum': return this.getNewPendulumForces(xPos, yPos, xVel, yVel); + case 'Spring' : return this.getNewSpringForces(yPos); + case 'Circular Motion': return this.getNewCircularMotionForces(xPos, yPos); + default: return this.props.forcesUpdated(); + } + }; + + // Update position, velocity using RK4 method + update = () => { + const startXVel = this.state.xVelocity; + const startYVel = this.state.yVelocity; + let xPos = this.state.xPosition; + let yPos = this.state.yPosition; + let xVel = this.state.xVelocity; + let yVel = this.state.yVelocity; + const forces = this.getForces(xPos, yPos, xVel, yVel); + const xAcc = this.getNewAccelerationX(forces); + const yAcc = this.getNewAccelerationY(forces); + const coeff = (this.props.timestepSize * 1.0) / 6.0; + for (let i = 0; i < this.props.simulationSpeed; i++) { + const k1 = this.evaluate(xPos, yPos, xVel, yVel, xVel, yVel, xAcc, yAcc, 0); + const k2 = this.evaluate(xPos, yPos, xVel, yVel, k1.deltaXPos, k1.deltaYPos, k1.deltaXVel, k1.deltaYVel, this.props.timestepSize * 0.5); + const k3 = this.evaluate(xPos, yPos, xVel, yVel, k2.deltaXPos, k2.deltaYPos, k2.deltaXVel, k2.deltaYVel, this.props.timestepSize * 0.5); + const k4 = this.evaluate(xPos, yPos, xVel, yVel, k3.deltaXPos, k3.deltaYPos, k3.deltaXVel, k3.deltaYVel, this.props.timestepSize); + + xVel += coeff * (k1.deltaXVel + 2 * (k2.deltaXVel + k3.deltaXVel) + k4.deltaXVel); + yVel += coeff * (k1.deltaYVel + 2 * (k2.deltaYVel + k3.deltaYVel) + k4.deltaYVel); + xPos += coeff * (k1.deltaXPos + 2 * (k2.deltaXPos + k3.deltaXPos) + k4.deltaXPos); + yPos += coeff * (k1.deltaYPos + 2 * (k2.deltaYPos + k3.deltaYPos) + k4.deltaYPos); + } + // make sure harmonic motion maintained and errors don't propagate + switch (this.props.simulationType) { + case 'Spring': + const equilibriumPos = this.props.springRestLength + (this.props.mass * this.props.gravity) / this.props.springConstant; + const amplitude = Math.abs(equilibriumPos - this.props.springStartLength); + if (startYVel < 0 && yVel > 0 && yPos < this.props.springRestLength) { + yPos = equilibriumPos - amplitude; + } else if (startYVel > 0 && yVel < 0 && yPos > this.props.springRestLength) { + yPos = equilibriumPos + amplitude; + } + break; + case 'Pendulum': + const startX = this.state.updatedStartPosX; + if (startXVel <= 0 && xVel > 0) { + xPos = this.state.updatedStartPosX; + if (this.state.updatedStartPosX > this.props.xMax / 2) { + xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius; + } + yPos = this.props.startPosY; + } else if (startXVel >= 0 && xVel < 0) { + xPos = this.state.updatedStartPosX; + if (this.state.updatedStartPosX < this.props.xMax / 2) { + xPos = this.props.xMax / 2 + (this.props.xMax / 2 - startX) - 2 * this.props.radius; + } + yPos = this.props.startPosY; + } + break; + case 'One Weight': + if (yPos < this.state.maxPosYConservation) { + yPos = this.state.maxPosYConservation; + } + } + this.setState({ xVelocity: xVel, yVelocity: yVel, xPosition: xPos, yPosition: yPos }); + + const forcesn = this.getForces(xPos, yPos, xVel, yVel); + this.props.setForcesUpdated(forcesn); + + // set component forces if they change + if (this.props.simulationType == 'Pendulum') { + const x = this.props.xMax / 2 - xPos - this.props.radius; + const y = yPos + this.props.radius + 5; + let angle = (Math.atan(y / x) * 180) / Math.PI; + if (angle < 0) { + angle += 180; + } + let oppositeAngle = 90 - angle; + if (oppositeAngle < 0) { + oppositeAngle = 90 - (180 - angle); + } + + const pendulumLength = Math.sqrt(x * x + y * y); + + const tensionComponent: IForce = { + description: 'Tension', + magnitude: this.props.mass * this.props.gravity * Math.cos((oppositeAngle * Math.PI) / 180) + (this.props.mass * (xVel * xVel + yVel * yVel)) / pendulumLength, + directionInDegrees: angle, + }; + const gravityParallel: IForce = { + description: 'Gravity Parallel Component', + magnitude: this.props.gravity * Math.cos(((90 - angle) * Math.PI) / 180), + directionInDegrees: 270 - (90 - angle), + }; + const gravityPerpendicular: IForce = { + description: 'Gravity Perpendicular Component', + magnitude: this.props.gravity * Math.sin(((90 - angle) * Math.PI) / 180), + directionInDegrees: -(90 - angle), + }; + if (this.props.gravity * Math.sin(((90 - angle) * Math.PI) / 180) < 0) { + gravityPerpendicular.magnitude = Math.abs(this.props.gravity * Math.sin(((90 - angle) * Math.PI) / 180)); + gravityPerpendicular.directionInDegrees = 180 - (90 - angle); + } + this.props.setComponentForces([tensionComponent, gravityParallel, gravityPerpendicular]); + } + }; + + renderForce = (force: IForce, index: number, asComponent: boolean, color = '#0d0d0d') => { + if (force.magnitude < this.epsilon) return; + + const angle = (force.directionInDegrees * Math.PI) / 180; + const arrowStartY = this.state.yPosition + this.props.radius - this.props.radius * Math.sin(angle); + const arrowStartX = this.state.xPosition + this.props.radius + this.props.radius * Math.cos(angle); + const arrowEndY = arrowStartY - Math.abs(force.magnitude) * Math.sin(angle) - this.props.radius * Math.sin(angle); + const arrowEndX = arrowStartX + Math.abs(force.magnitude) * Math.cos(angle) + this.props.radius * Math.cos(angle); + + let labelTop = arrowEndY + (force.directionInDegrees >= 0 && force.directionInDegrees < 180 ? 40 : -40); + let labelLeft = arrowEndX + (force.directionInDegrees > 90 && force.directionInDegrees < 270 ? -120 : 30); + + labelTop = Math.max(Math.min(labelTop, this.props.yMax + 50), this.props.yMin); + labelLeft = Math.max(Math.min(labelLeft, this.props.xMax - 60), this.props.xMin); + + return ( +
+
+ + + + + + + + +
+
+

{force.description || 'Force'}

+ {this.props.showForceMagnitudes &&

{Math.round(100 * force.magnitude) / 100} N

} +
+
+ ); + }; + + renderVector = (id: string, magX: number, magY: number, color: string, label: string) => { + const mag = Math.sqrt(magX * magX + magY * magY); + return ( +
+ + + + + + + + +
+

{label}

+
+
+ ); + }; + + // Render weight, spring, rod(s), vectors + render() { + return ( +
+
{ + if (this.draggable) { + this.props.pause(); + this.setState({ + dragging: true, + clickPositionX: e.clientX, + clickPositionY: e.clientY, + }); + } + }} + onPointerMove={e => { + if (this.state.dragging) { + let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; + if (newY > this.props.yMax - 2 * this.props.radius - 10) { + newY = this.props.yMax - 2 * this.props.radius - 10; + } else if (newY < 10) { + newY = 10; + } + + let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; + if (newX > this.props.xMax - 2 * this.props.radius - 10) { + newX = this.props.xMax - 2 * this.props.radius - 10; + } else if (newX < 10) { + newX = 10; + } + if (this.props.simulationType == 'Suspension') { + if (newX < (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15) { + newX = (this.props.xMax + this.props.xMin) / 4 - this.props.radius - 15; + } else if (newX > (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15) { + newX = (3 * (this.props.xMax + this.props.xMin)) / 4 - this.props.radius / 2 - 15; + } + } + + this.setState({ yPosition: newY }); + this.props.setPosition(undefined, Math.round((this.props.yMax - 2 * this.props.radius - newY + 5) * 100) / 100); + if (this.props.simulationType != 'Pulley') { + this.setState({ xPosition: newX }); + this.props.setPosition(newX, undefined); + } + if (this.props.simulationType != 'Suspension') { + if (this.props.simulationType != 'Pulley') { + this.setState({ updatedStartPosX: newX }); + } + this.setState({ updatedStartPosY: newY }); + } + this.setState({ + clickPositionX: e.clientX, + clickPositionY: e.clientY, + }); + this.setDisplayValues(); + } + }} + onPointerUp={e => { + if (this.state.dragging) { + if (this.props.simulationType != 'Pendulum' && this.props.simulationType != 'Suspension') { + this.resetEverything(); + } + this.setState({ dragging: false }); + let newY = this.state.yPosition + e.clientY - this.state.clickPositionY; + if (newY > this.props.yMax - 2 * this.props.radius - 10) { + newY = this.props.yMax - 2 * this.props.radius - 10; + } else if (newY < 10) { + newY = 10; + } + + let newX = this.state.xPosition + e.clientX - this.state.clickPositionX; + if (newX > this.props.xMax - 2 * this.props.radius - 10) { + newX = this.props.xMax - 2 * this.props.radius - 10; + } else if (newX < 10) { + newX = 10; + } + if (this.props.simulationType == 'Spring') { + this.props.setSpringLength(newY); + } + if (this.props.simulationType == 'Suspension') { + const x1rod = (this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200; + const x2rod = (this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius; + const deltaX1 = this.state.xPosition + this.props.radius - x1rod; + const deltaX2 = x2rod - (this.state.xPosition + this.props.radius); + const deltaY = this.state.yPosition + this.props.radius; + const dir1T = Math.PI - Math.atan(deltaY / deltaX1); + const dir2T = Math.atan(deltaY / deltaX2); + const tensionMag2 = (this.props.mass * this.props.gravity) / ((-Math.cos(dir2T) / Math.cos(dir1T)) * Math.sin(dir1T) + Math.sin(dir2T)); + const tensionMag1 = (-tensionMag2 * Math.cos(dir2T)) / Math.cos(dir1T); + const tensionForce1: IForce = { + description: 'Tension', + magnitude: tensionMag1, + directionInDegrees: (dir1T * 180) / Math.PI, + }; + const tensionForce2: IForce = { + description: 'Tension', + magnitude: tensionMag2, + directionInDegrees: (dir2T * 180) / Math.PI, + }; + this.props.setForcesUpdated([tensionForce1, tensionForce2, this.gravityForce()]); + } + } + }}> +
+

{this.props.mass} kg

+
+
+ {this.props.simulationType == 'Spring' && ( +
+ + {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(val => { + const count = 10; + const xPos1 = this.state.xPosition + this.props.radius + (val % 2 === 0 ? -20 : 20); + const xPos2 = this.state.xPosition + this.props.radius + (val === 10 ? 0 : val % 2 === 0 ? 20 : -20); + const yPos1 = (val * this.state.yPosition) / count; + const yPos2 = val === 10 ? this.state.yPosition + this.props.radius : ((val + 1) * this.state.yPosition) / count; + return ; + })} + +
+ )} + + {this.props.simulationType == 'Pulley' && ( +
+ + + +
+ )} + {this.props.simulationType == 'Pulley' && ( +
+ + + +
+ )} + {this.props.simulationType == 'Suspension' && ( +
+ + + +

+ {Math.round( + ((Math.atan((this.state.yPosition + this.props.radius) / (this.state.xPosition + this.props.radius - ((this.props.xMax + this.props.xMin) / 2 - this.props.radius - this.props.yMin - 200))) * 180) / Math.PI) * 100 + ) / 100} + ° +

+
+ + + +
+

+ {Math.round( + ((Math.atan((this.state.yPosition + this.props.radius) / ((this.props.xMax + this.props.xMin) / 2 + this.props.yMin + 200 + this.props.radius - (this.state.xPosition + this.props.radius))) * 180) / Math.PI) * 100 + ) / 100} + ° +

+
+ )} + {this.props.simulationType == 'Circular Motion' && ( +
+ + + +
+ )} + {this.props.simulationType == 'Pendulum' && ( +
+ + + + {!this.state.dragging && ( +
+

+ {Math.round(this.props.pendulumLength)} m +

+

+ {Math.round(this.props.pendulumAngle * 100) / 100}° +

+
+ )} +
+ )} + {this.props.simulationType == 'Inclined Plane' && ( +
+
+ + + +
+

+ {Math.round(((Math.atan(this.props.wedgeHeight / this.props.wedgeWidth) * 180) / Math.PI) * 100) / 100}° +

+
+ )} + {!this.state.dragging && + this.props.showAcceleration && + this.renderVector( + 'accArrow', + this.getNewAccelerationX(this.props.forcesUpdated()), + this.getNewAccelerationY(this.props.forcesUpdated()), + 'green', + `${Math.round(100 * Math.sqrt(this.state.xAccel * this.state.xAccel + this.state.yAccel * this.state.yAccel)) / 100} m/s^2` + )} + {!this.state.dragging && + this.props.showVelocity && + this.renderVector( + 'velArrow', + this.state.xVelocity, + this.state.yVelocity, + 'blue', + `${Math.round(100 * Math.sqrt(this.props.displayXVelocity * this.props.displayXVelocity + this.props.displayYVelocity * this.props.displayYVelocity)) / 100} m/s` + )} + {!this.state.dragging && this.props.showComponentForces && this.props.componentForces().map((force, index) => this.renderForce(force, index, true))} + {!this.state.dragging && this.props.showForces && this.props.forcesUpdated().map((force, index) => this.renderForce(force, index, false))} +
+ ); + } +} diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts new file mode 100644 index 000000000..65decc147 --- /dev/null +++ b/src/fields/DocSymbols.ts @@ -0,0 +1,26 @@ +export const Update = Symbol('DocUpdate'); +export const Self = Symbol('DocSelf'); +export const SelfProxy = Symbol('DocSelfProxy'); +export const FieldKeys = Symbol('DocFieldKeys'); +export const FieldTuples = Symbol('DocFieldTuples'); +export const Width = Symbol('DocWidth'); +export const Height = Symbol('DocHeight'); +export const Animation = Symbol('DocAnimation'); +export const Highlight = Symbol('DocHighlight'); +export const DocData = Symbol('DocData'); +export const DocLayout = Symbol('DocLayout'); +export const DocFields = Symbol('DocFields'); +export const DocCss = Symbol('DocCss'); +export const DocAcl = Symbol('DocAcl'); +export const DirectLinks = Symbol('DocDirectLinks'); +export const AclUnset = Symbol('DocAclUnset'); +export const AclPrivate = Symbol('DocAclOwnerOnly'); +export const AclReadonly = Symbol('DocAclReadOnly'); +export const AclAugment = Symbol('DocAclAugment'); +export const AclSelfEdit = Symbol('DocAclSelfEdit'); +export const AclEdit = Symbol('DocAclEdit'); +export const AclAdmin = Symbol('DocAclAdmin'); +export const UpdatingFromServer = Symbol('DocUpdatingFromServer'); +export const Initializing = Symbol('DocInitializing'); +export const ForceServerWrite = Symbol('DocForceServerWrite'); +export const CachedUpdates = Symbol('DocCachedUpdates'); -- cgit v1.2.3-70-g09d2 From 6a85c8d4115a968f563369516d38549745e76303 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 24 Aug 2023 12:47:57 -0400 Subject: All working except createnote undo, and center on selected pin getAnchor --- src/client/views/SidebarAnnos.tsx | 5 ++- src/client/views/nodes/MapBox/MapAnchorMenu.tsx | 2 +- src/client/views/nodes/MapBox/MapBox.tsx | 47 ++++++++++++++++++------- src/fields/Doc.ts | 4 +-- 4 files changed, 40 insertions(+), 18 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index cd50865fb..7635d719e 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -225,10 +225,9 @@ export class SidebarAnnos extends React.Component {
e.stopPropagation()}> {this.allUsers.map(renderUsers)} {this.allHashtags.map(renderTag)} - {Array.from(this.allMetadata.keys()) + {/* {Array.from(this.allMetadata.keys()) .sort() - .map(key => renderMeta(key, this.allMetadata.get(key)))} - Hello + .map(key => renderMeta(key, this.allMetadata.get(key)))} */}
diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index 439c1f14f..897bb90a6 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -120,7 +120,7 @@ export class MapAnchorMenu extends AntimodeMenu { } + icon={} color={StrCast(Doc.UserDoc().userColor)} /> )} diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 654d446a2..dbb38e763 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -14,6 +14,7 @@ import { ScriptField } from '../../../../fields/ScriptField'; import { NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; +import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; @@ -352,10 +353,16 @@ export class MapBox extends ViewBoxAnnotatableComponent { + createNoteAnnotation = undoable(() => { !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); - setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false))); // give time for sidebarRef to be created - }; + setTimeout(() =>{ + const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false)); + if (note && this.selectedPin) { + note.latitude = this.selectedPin.latitude; + note.longitude = this.selectedPin.latitude; + } + }); // give time for sidebarRef to be created + }, "create linked note"); sidebarDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true); @@ -488,7 +495,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { + createPushpin = undoable((latitude: number, longitude: number) => { // Stores the pushpin as a MapMarkerDocument const mapMarker = Docs.Create.PushpinDocument( NumCast(latitude), @@ -501,7 +508,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this.selectedPin) { + // Removes filter + Doc.setDocFilter(this.rootDoc, "latitude", this.selectedPin.latitude, "remove"); + Doc.setDocFilter(this.rootDoc, "longitude", this.selectedPin.longitude, "remove"); + const temp = this.selectedPin; this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp)); const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude)); @@ -527,10 +538,15 @@ export class MapBox extends ViewBoxAnnotatableComponent this._sidebarRef.current?.makeDocUnfiltered(this._sidebarRef.current.)); + } + }; + getView = async (doc: Doc) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); + return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + }; /* * Pushpin onclick */ @@ -539,6 +555,9 @@ export class MapBox extends ViewBoxAnnotatableComponent creates a pushpin + * Map OnClick */ @action mapOnClick = (e: { location: { latitude: any; longitude: any } }) => { @@ -700,11 +718,16 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this.selectedPin) { + // Removes filter + Doc.setDocFilter(this.rootDoc, "latitude", this.selectedPin.latitude, "remove"); + Doc.setDocFilter(this.rootDoc, "longitude", this.selectedPin.longitude, "remove"); + this.removePushpin(this.selectedPin); } MapAnchorMenu.Instance.fadeOut(true); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6d4f4180d..7ba4f0e6f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1490,8 +1490,8 @@ export namespace Doc { runInAction(() => { for (let i = 0; i < childFilters.length; i++) { const fields = childFilters[i].split(FilterSep); // split key:value:modifier - if (fields[0] === key && (fields[1] === value || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { - if (fields[2] === modifiers && modifiers && fields[1] === value) { + if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { + if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) { if (toggle) modifiers = 'remove'; else return; } -- cgit v1.2.3-70-g09d2 From 87f2cb52f59f3441ad3d46adc135492ae9924b89 Mon Sep 17 00:00:00 2001 From: srichman333 Date: Thu, 24 Aug 2023 14:59:36 -0400 Subject: context menu updates --- src/client/documents/Documents.ts | 2 +- src/fields/Doc.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src/fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f5fa38ec6..85119a0be 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1567,7 +1567,7 @@ export namespace DocUtils { const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) .filter(btnDoc => !btnDoc.hidden) .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) - .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyDataViz) + .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyNote && doc.title && doc.title != '') .map((dragDoc, i) => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), event: undoable((args: { x: number; y: number }) => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ceacb8a08..92d1aef83 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1577,12 +1577,13 @@ export namespace Doc { // prettier-ignore export function toIcon(doc?: Doc, isOpen?: boolean) { + console.log(doc!.title, doc!.type) switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) { case DocumentType.IMG: return 'image'; case DocumentType.COMPARISON: return 'columns'; case DocumentType.RTF: return 'sticky-note'; case DocumentType.COL: - const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : 'question'; + const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc!.title=='Untitled Collection'? 'object-group': 'chalkboard'; const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question'; return !doc?.isFolder ? folder : chevron; case DocumentType.WEB: return 'globe-asia'; @@ -1598,6 +1599,10 @@ export namespace Doc { case DocumentType.PDF: return 'file-pdf'; case DocumentType.LINK: return 'link'; case DocumentType.MAP: return 'map-marker-alt'; + case DocumentType.DATAVIZ: return 'chart-bar'; + case DocumentType.EQUATION: return 'calculator'; + case DocumentType.SIMULATION: return 'rocket'; + case DocumentType.CONFIG: return 'question-circle'; default: return 'question'; } } -- cgit v1.2.3-70-g09d2 From bf1777b93be0707e17e3b3c0ca6c965facebfe14 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 27 Aug 2023 02:50:01 -0400 Subject: fixed filters to filter by linkedTo instead of lat/lng. made links to pushpins when anything is added to sidebar and pushpin is selected --- src/client/documents/Documents.ts | 32 +++++--- src/client/views/MainView.tsx | 40 +++++++++- src/client/views/SidebarAnnos.tsx | 8 +- src/client/views/nodes/MapBox/MapBox.tsx | 127 ++++++++++++++++--------------- src/client/views/pdf/PDFViewer.tsx | 2 +- src/fields/Doc.ts | 6 +- src/fields/documentSchemas.ts | 11 ++- 7 files changed, 140 insertions(+), 86 deletions(-) (limited to 'src/fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index add12896e..ceee8c76f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1075,8 +1075,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MAP), new List(documents), options); } - export function PushpinDocument(lat: number, lng: number, infoWindowOpen: boolean, documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude: lat, longitude: lng, infoWindowOpen, ...options }, id); + export function PushpinDocument(latitude: number, longitude: number, infoWindowOpen: boolean, documents: Array, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.PUSHPIN), new List(documents), { latitude, longitude, infoWindowOpen, ...options }, id); } // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView) @@ -1097,9 +1097,6 @@ export namespace Docs { export function HTMLMarkerDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Freeform }, id); } - export function MapMarkerDocument(lat: number, lng: number, infoWindowOpen: boolean, documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { latitude: lat, longitude: lng, infoWindowOpen, ...options, _type_collection: CollectionViewType.Freeform }, id); - } export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto( @@ -1274,10 +1271,23 @@ export namespace DocUtils { if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { return false; } - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) { const facet = filterFacets[facetKey]; + let links = true; + const linkedTo = filterFacets['-linkedTo'] && Array.from(Object.keys(filterFacets['-linkedTo']))?.[0]; + const linkedToField = filterFacets['-linkedTo']?.[linkedTo]; + const allLinks = linkedTo && linkedToField ? LinkManager.Instance.getAllRelatedLinks(d) : []; + // prettier-ignore + if (linkedTo) { + if (allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_1)?.[linkedToField]))) || // + allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_2)?.[linkedToField])))) + { + links = true; + } + else links = false + } + // facets that match some value in the field of the document (e.g. some text field) const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); @@ -1293,7 +1303,7 @@ export namespace DocUtils { // facets that have an x next to them const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; + if (!linkedTo && !unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined); @@ -1312,11 +1322,11 @@ export namespace DocUtils { }); // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria if (parentCollection?.childFilters_boolean === 'OR') { - if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; + if (links && satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; } // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria else { - if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; + if (!links || !satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; } } return parentCollection?.childFilters_boolean === 'OR' ? false : true; @@ -1800,8 +1810,8 @@ export namespace DocUtils { const longitude = result.exifData?.data?.GPSLongitude; const longitudeDirection = result.exifData?.data?.GPSLongitudeRef; if (latitude !== undefined && longitude !== undefined && latitudeDirection !== undefined && longitudeDirection !== undefined) { - proto.lat = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); - proto.lng = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); + proto.latitude = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); + proto.longitude = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); } } if (Upload.isVideoInformation(result)) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 344a401f3..2a9f0cc02 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -60,6 +60,7 @@ import { ImageBox } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview } from './nodes/LinkDocPreview'; import { MapAnchorMenu } from './nodes/MapBox/MapAnchorMenu'; +import { MapBox } from './nodes/MapBox/MapBox'; import { RadialMenu } from './nodes/RadialMenu'; import { TaskCompletionBox } from './nodes/TaskCompletedBox'; import { OverlayView } from './OverlayView'; @@ -956,6 +957,42 @@ export class MainView extends React.Component { @computed get linkDocPreview() { return LinkDocPreview.LinkInfo ? : null; } + @observable mapBoxHackBool = false; + @computed get mapBoxHack() { + return this.mapBoxHackBool ? null : ( + r && (this.mapBoxHackBool = true))} + fieldKey="data" + select={returnFalse} + isSelected={returnFalse} + Document={this.headerBarDoc} + DataDoc={undefined} + addDocTab={returnFalse} + pinToPres={emptyFunction} + docViewPath={returnEmptyDoclist} + styleProvider={DefaultStyleProvider} + rootSelected={returnTrue} + addDocument={returnFalse} + removeDocument={returnFalse} + fitContentsToBox={returnTrue} + isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events) + isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive + ScreenToLocalTransform={Transform.Identity} + childHideResizeHandles={returnTrue} + childDragAction="move" + dontRegisterView={true} + PanelWidth={this.headerBarDocWidth} + PanelHeight={this.headerBarDocHeight} + renderDepth={0} + focus={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} + bringToFront={emptyFunction} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + /> + ); + } render() { return ( @@ -1011,7 +1048,7 @@ export class MainView extends React.Component { - + @@ -1020,6 +1057,7 @@ export class MainView extends React.Component { {this.snapLines} + {this.mapBoxHack} {/* */} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 520485a71..c48a7241a 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -66,7 +66,7 @@ export class SidebarAnnos extends React.Component { return '_' + this.sidebarKey + '_childFilters'; } - anchorMenuClick = (anchor: Doc) => { + anchorMenuClick = (anchor: Doc, filterExlusions?: string[]) => { const startup = StrListCast(this.props.rootDoc.childFilters) .map(filter => filter.split(':')[0]) .join(' '); @@ -79,6 +79,7 @@ export class SidebarAnnos extends React.Component { _layout_autoHeight: true, _text_fontSize: StrCast(Doc.UserDoc().fontSize), _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), + target: 'HELLO' as any, }); FormattedTextBox.SelectOnLoad = target[Id]; FormattedTextBox.DontSelectInitialText = true; @@ -87,7 +88,7 @@ export class SidebarAnnos extends React.Component { const taggedContent = this.childFilters() .filter(data => data.split(':')[0]) - .filter(data => data.split(':')[0] !== 'latitude' && data.split(':')[0] !== 'longitude') + .filter(data => !filterExlusions?.includes(data.split(':')[0])) .map(data => { const key = data.split(':')[0]; const val = Field.Copy(this.allMetadata.get(key)); @@ -188,7 +189,7 @@ export class SidebarAnnos extends React.Component { }; render() { const renderTag = (tag: string) => { - const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags:${tag}:check`); + const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags::${tag}::check`); return (
Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, this.sidebarKey, e.shiftKey)}> {tag} @@ -231,7 +232,6 @@ export class SidebarAnnos extends React.Component { .map(key => renderMeta(key, this.allMetadata.get(key)))} */}
-
(); - @computed get inlineTextAnnotations() { - return this.allMapMarkers.filter(a => a.text_inlineAnnotations); - } @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } - @computed get allMapMarkers() { + // this list contains pushpins and configs + @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } + @computed get allPushpins() { + return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); + } @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } @@ -113,14 +116,20 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (doc.lat !== undefined && doc.lng !== undefined) { - const existingMarker = this.allMapMarkers.find(marker => marker.lat === doc.lat && marker.lng === doc.lng); - if (existingMarker) { - Doc.AddDocToList(existingMarker, 'data', doc); - } else { - const marker = Docs.Create.PushpinDocument(NumCast(doc.lat), NumCast(doc.lng), false, [doc], {}); - this.addDocument(marker, this.annotationKey); - } + let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPin; + if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) { + existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map)); + } + if (existingPin) { + setTimeout(() => { + // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet + if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) { + const anchor = this.getAnchor(true, undefined, existingPin); + anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' }); + doc.latitude = existingPin?.latitude; + doc.longitude = existingPin?.longitude; + } + }); } }); //add to annotation list @@ -199,7 +208,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { const createFunc = undoable( action(() => { - const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false)); + const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(false), ['latitude', 'longitude', '-linkedTo']); if (note && this.selectedPin) { note.latitude = this.selectedPin.latitude; note.longitude = this.selectedPin.longitude; @@ -309,7 +318,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { // Stores the pushpin as a MapMarkerDocument - const mapMarker = Docs.Create.PushpinDocument( + const pushpin = Docs.Create.PushpinDocument( NumCast(latitude), NumCast(longitude), false, @@ -317,8 +326,8 @@ export class MapBox extends ViewBoxAnnotatableComponent { + getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => { /// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER const anchor = Docs.Create.ConfigDocument({ title: 'MapAnchor:' + this.rootDoc.title, text: StrCast(this.selectedPin?.map) || StrCast(this.rootDoc.map) || 'map location', - config_latitude: NumCast(this.selectedPin?.latitude ?? this.dataDoc.latitude), - config_longitude: NumCast(this.selectedPin?.longitude ?? this.dataDoc.longitude), + config_latitude: NumCast((existingPin ?? this.selectedPin)?.latitude ?? this.dataDoc.latitude), + config_longitude: NumCast((existingPin ?? this.selectedPin)?.longitude ?? this.dataDoc.longitude), config_map_zoom: NumCast(this.dataDoc.map_zoom), config_map_type: StrCast(this.dataDoc.map_type), - config_map: StrCast(this.selectedPin?.map) || StrCast(this.dataDoc.map), + config_map: StrCast((existingPin ?? this.selectedPin)?.map) || StrCast(this.dataDoc.map), layout_unrendered: true, }); if (anchor) { - anchor.mapPin = this.selectedPin; + anchor.mapPin = existingPin ?? this.selectedPin; if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; /* addAsAnnotation &&*/ this.addDocument(anchor); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.rootDoc); @@ -495,6 +499,7 @@ export class MapBox extends ViewBoxAnnotatableComponent this.allMapPushpins.map(doc => doc[Highlight]), + () => this.allAnnotations.map(doc => doc[Highlight]), () => { - const allPins = this.allMapPushpins.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin); - allPins.forEach(({ doc, pushpin }) => { + const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin); + allConfigPins.forEach(({ doc, pushpin }) => { if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) { this.recolorPin(pushpin); this.map_pinHighlighted.delete(pushpin); } }); - allPins.forEach(({ doc, pushpin }) => { + allConfigPins.forEach(({ doc, pushpin }) => { if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) { this.recolorPin(pushpin, 'orange'); this.map_pinHighlighted.set(pushpin, true); @@ -737,30 +742,32 @@ export class MapBox extends ViewBoxAnnotatableComponent {!this._mapReady ? null - : this.allMapPushpins.map(pushpin => ( - - ))} + : this.allAnnotations + .filter(anno => !anno.layout_unrendered) + .map(pushpin => ( + + ))}
{/* boolean; loaded?: (nw: number, nh: number, np: number) => void; setPdfViewer: (view: PDFViewer) => void; - anchorMenuClick?: () => undefined | ((anchor: Doc, summarize?: boolean) => void); + anchorMenuClick?: () => undefined | ((anchor: Doc) => void); crop: (region: Doc | undefined, addCrop?: boolean) => Doc | undefined; } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7ba4f0e6f..501114157 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1483,14 +1483,14 @@ export namespace Doc { // filters document in a container collection: // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined - export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { + export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'removeAll' | 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { if (!container) return; const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; const childFilters = StrListCast(container[filterField]); runInAction(() => { for (let i = 0; i < childFilters.length; i++) { const fields = childFilters[i].split(FilterSep); // split key:value:modifier - if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { + if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || modifiers === 'removeAll' || (fields[2] === 'match' && modifiers === 'remove'))) { if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) { if (toggle) modifiers = 'remove'; else return; @@ -1502,7 +1502,7 @@ export namespace Doc { } if (!childFilters.length && modifiers === 'match' && value === undefined) { container[filterField] = undefined; - } else if (modifiers !== 'remove') { + } else if (modifiers !== 'remove' && modifiers !== 'removeAll') { !append && (childFilters.length = 0); childFilters.push(key + FilterSep + value + FilterSep + modifiers); container[filterField] = new List(childFilters); diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index e33a17416..8eeb52709 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -1,8 +1,7 @@ -import { makeInterface, createSchema, listSpec } from './Schema'; -import { ScriptField } from './ScriptField'; -import { Doc } from './Doc'; import { DateField } from './DateField'; -import { SchemaHeaderField } from './SchemaHeaderField'; +import { Doc } from './Doc'; +import { createSchema, listSpec, makeInterface } from './Schema'; +import { ScriptField } from './ScriptField'; export const documentSchema = createSchema({ // content properties @@ -26,8 +25,8 @@ export const documentSchema = createSchema({ z: 'number', // z "coordinate" - non-zero specifies the overlay layer of a freeformview zIndex: 'number', // zIndex of a document in a freeform view _layout_scrollTop: 'number', // scroll position of a scrollable document (pdf, text, web) - lat: 'number', - lng: 'number', + latitude: 'number', + longitude: 'number', // appearance properties on the layout '_backgroundGrid-spacing': 'number', // the size of the grid for collection views -- cgit v1.2.3-70-g09d2 From a429e628d0053f087270638756bd72c55da2e0d9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 27 Aug 2023 12:16:05 -0400 Subject: removed icloud files --- src/client/util/.ReportManager.scss.icloud | Bin 168 -> 0 bytes src/client/util/.ReportManager.tsx.icloud | Bin 167 -> 0 bytes src/client/views/.Palette.scss.icloud | Bin 160 -> 0 bytes src/client/views/nodes/.QueryBox.scss.icloud | Bin 160 -> 0 bytes src/client/views/nodes/.QueryBox.tsx.icloud | Bin 160 -> 0 bytes src/fields/.ListSpec.ts.icloud | Bin 158 -> 0 bytes src/server/stats/.userLoginStats.csv.icloud | Bin 168 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/client/util/.ReportManager.scss.icloud delete mode 100644 src/client/util/.ReportManager.tsx.icloud delete mode 100644 src/client/views/.Palette.scss.icloud delete mode 100644 src/client/views/nodes/.QueryBox.scss.icloud delete mode 100644 src/client/views/nodes/.QueryBox.tsx.icloud delete mode 100644 src/fields/.ListSpec.ts.icloud delete mode 100644 src/server/stats/.userLoginStats.csv.icloud (limited to 'src/fields') diff --git a/src/client/util/.ReportManager.scss.icloud b/src/client/util/.ReportManager.scss.icloud deleted file mode 100644 index f5d34d292..000000000 Binary files a/src/client/util/.ReportManager.scss.icloud and /dev/null differ diff --git a/src/client/util/.ReportManager.tsx.icloud b/src/client/util/.ReportManager.tsx.icloud deleted file mode 100644 index 72924c53a..000000000 Binary files a/src/client/util/.ReportManager.tsx.icloud and /dev/null differ diff --git a/src/client/views/.Palette.scss.icloud b/src/client/views/.Palette.scss.icloud deleted file mode 100644 index 49a2ac2da..000000000 Binary files a/src/client/views/.Palette.scss.icloud and /dev/null differ diff --git a/src/client/views/nodes/.QueryBox.scss.icloud b/src/client/views/nodes/.QueryBox.scss.icloud deleted file mode 100644 index 26ba46a75..000000000 Binary files a/src/client/views/nodes/.QueryBox.scss.icloud and /dev/null differ diff --git a/src/client/views/nodes/.QueryBox.tsx.icloud b/src/client/views/nodes/.QueryBox.tsx.icloud deleted file mode 100644 index 5930b1e0e..000000000 Binary files a/src/client/views/nodes/.QueryBox.tsx.icloud and /dev/null differ diff --git a/src/fields/.ListSpec.ts.icloud b/src/fields/.ListSpec.ts.icloud deleted file mode 100644 index 2fbd1858e..000000000 Binary files a/src/fields/.ListSpec.ts.icloud and /dev/null differ diff --git a/src/server/stats/.userLoginStats.csv.icloud b/src/server/stats/.userLoginStats.csv.icloud deleted file mode 100644 index 4a95297e8..000000000 Binary files a/src/server/stats/.userLoginStats.csv.icloud and /dev/null differ -- cgit v1.2.3-70-g09d2 From 62826dfca7e2d1785c4d808544023d1d5708d611 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 27 Aug 2023 13:44:36 -0400 Subject: fromlast --- .../views/nodes/DataVizBox/components/TableBox.tsx | 7 +++---- src/fields/Doc.ts | 23 ++++++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 6688bcedb..5045fde3a 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,17 +1,16 @@ +import { Button, Type } from 'browndash-components'; import { action, computed, IReactionDisposer, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, Field, NumListCast, StrListCast } from '../../../../../fields/Doc'; +import { Doc, Field, NumListCast } from '../../../../../fields/Doc'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast, DocCast } from '../../../../../fields/Types'; -import { emptyFunction, numberRange, setupMoveUpEvents, Utils } from '../../../../../Utils'; +import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils'; import { DragManager } from '../../../../util/DragManager'; -import { LinkManager } from '../../../../util/LinkManager'; import { DocumentView } from '../../DocumentView'; import { DataVizView } from '../DataVizBox'; import './Chart.scss'; -import { Button, Type } from 'browndash-components'; interface TableBoxProps { rootDoc: Doc; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index f242ab5be..c8564e5bb 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1461,37 +1461,35 @@ export namespace Doc { prevLayout === 'icon' && (doc.deiconifyLayout = undefined); doc.layout_fieldKey = deiconify || 'layout'; } - export function setDocRangeFilter(container: Opt, key: string, range?: readonly number[], modifiers?: 'remove') { //, modifiers: 'remove' | 'set' + export function setDocRangeFilter(container: Opt, key: string, range?: readonly number[], modifiers?: 'remove') { + //, modifiers: 'remove' | 'set' if (!container) return; const childFiltersByRanges = Cast(container._childFiltersByRanges, listSpec('string'), []); - - - for (let i = 0; i < childFiltersByRanges.length; i += 3) { if (childFiltersByRanges[i] === key) { - console.log("this is key inside childfilters by range " + key) + console.log('this is key inside childfilters by range ' + key); childFiltersByRanges.splice(i, 3); - console.log("this is child filters by range " + childFiltersByRanges) + console.log('this is child filters by range ' + childFiltersByRanges); break; } } if (range !== undefined) { - console.log("in doc.ts in set range filter") + console.log('in doc.ts in set range filter'); childFiltersByRanges.push(key); childFiltersByRanges.push(range[0].toString()); childFiltersByRanges.push(range[1].toString()); container._childFiltersByRanges = new List(childFiltersByRanges); - console.log("this is child filters by range "+ childFiltersByRanges[0] + "," + childFiltersByRanges[1] + "," + childFiltersByRanges[2]) - console.log("this is new list " + container._childFiltersByRange) + console.log('this is child filters by range ' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]); + console.log('this is new list ' + container._childFiltersByRange); } - if (modifiers){ - childFiltersByRanges.splice(0,3) + if (modifiers) { + childFiltersByRanges.splice(0, 3); container._childFiltersByRanges = new List(childFiltersByRanges); } - console.log("this is child filters by range END"+ childFiltersByRanges[0] + "," + childFiltersByRanges[1] + "," + childFiltersByRanges[2]) + console.log('this is child filters by range END' + childFiltersByRanges[0] + ',' + childFiltersByRanges[1] + ',' + childFiltersByRanges[2]); } export const FilterSep = '::'; @@ -1600,7 +1598,6 @@ export namespace Doc { // prettier-ignore export function toIcon(doc?: Doc, isOpen?: boolean) { - console.log(doc!.title, doc!.type) switch (isOpen !== undefined ? DocumentType.COL: StrCast(doc?.type)) { case DocumentType.IMG: return 'image'; case DocumentType.COMPARISON: return 'columns'; -- cgit v1.2.3-70-g09d2 From efc6e14ea9019ee16813f69aa195119638921523 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 27 Aug 2023 14:28:57 -0400 Subject: from last --- src/fields/Doc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/fields') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index c8564e5bb..09aac03ea 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1603,7 +1603,7 @@ export namespace Doc { case DocumentType.COMPARISON: return 'columns'; case DocumentType.RTF: return 'sticky-note'; case DocumentType.COL: - const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc!.title=='Untitled Collection'? 'object-group': 'chalkboard'; + const folder: IconProp = isOpen === true ? 'folder-open' : isOpen === false ? 'folder' : doc?.title==='Untitled Collection'? 'object-group': 'chalkboard'; const chevron: IconProp = isOpen === true ? 'chevron-down' : isOpen === false ? 'chevron-right' : 'question'; return !doc?.isFolder ? folder : chevron; case DocumentType.WEB: return 'globe-asia'; -- cgit v1.2.3-70-g09d2 From 9405a97acabb70ac671029f61e825ba00a8dc3ce Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 27 Aug 2023 14:37:42 -0400 Subject: breaking include cycles --- src/client/util/SelectionManager.ts | 31 +++++++++++++++++++++++++++++-- src/fields/Doc.ts | 30 +++--------------------------- 2 files changed, 32 insertions(+), 29 deletions(-) (limited to 'src/fields') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 4be9448b3..dbf33fcf5 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,11 +1,14 @@ import { action, observable, ObservableMap } from 'mobx'; import { computedFn } from 'mobx-utils'; import { Doc, Opt } from '../../fields/Doc'; -import { DocCast } from '../../fields/Types'; -import { CollectionViewType } from '../documents/DocumentTypes'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { Cast, DocCast } from '../../fields/Types'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; +import { UndoManager } from './UndoManager'; export namespace SelectionManager { class Manager { @@ -124,3 +127,27 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp ScriptingGlobals.add(function deselectAll() { SelectionManager.DeselectAll(); }); +ScriptingGlobals.add(function undo() { + SelectionManager.DeselectAll(); + return UndoManager.Undo(); +}); + +export function ShowUndoStack() { + SelectionManager.DeselectAll(); + var buffer = ''; + UndoManager.undoStack.forEach((batch, i) => { + buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n'; + ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); + }); + alert(buffer); +} +ScriptingGlobals.add(function redo() { + SelectionManager.DeselectAll(); + return UndoManager.Redo(); +}); +ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { + const docs = SelectionManager.Views() + .map(dv => dv.props.Document) + .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); + return docs.length ? new List(docs) : prevValue; +}); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 09aac03ea..4ad38e7fc 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -7,9 +7,8 @@ import { DocServer } from '../client/DocServer'; import { DocumentType } from '../client/documents/DocumentTypes'; import { LinkManager } from '../client/util/LinkManager'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; -import { SelectionManager } from '../client/util/SelectionManager'; import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from '../client/util/SerializationHelper'; -import { undoable, UndoManager } from '../client/util/UndoManager'; +import { undoable } from '../client/util/UndoManager'; import { decycle } from '../decycler/decycler'; import * as JSZipUtils from '../JSZipUtils'; import { DashColor, incrementTitleCopy, intersectRect, Utils } from '../Utils'; @@ -50,7 +49,7 @@ import { listSpec } from './Schema'; import { ComputedField, ScriptField } from './ScriptField'; import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; -import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, containedFieldChangedHandler } from './util'; +import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions } from './util'; import JSZip = require('jszip'); export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -1830,24 +1829,6 @@ ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function undo() { - SelectionManager.DeselectAll(); - return UndoManager.Undo(); -}); - -export function ShowUndoStack() { - SelectionManager.DeselectAll(); - var buffer = ''; - UndoManager.undoStack.forEach((batch, i) => { - buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n'; - ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); - }); - alert(buffer); -} -ScriptingGlobals.add(function redo() { - SelectionManager.DeselectAll(); - return UndoManager.Redo(); -}); ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return 'invalid'; @@ -1862,12 +1843,7 @@ ScriptingGlobals.add(function activePresentationItem() { const curPres = Doc.ActivePresentation; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; }); -ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { - const docs = SelectionManager.Views() - .map(dv => dv.props.Document) - .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); - return docs.length ? new List(docs) : prevValue; -}); + ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: any, modifiers: 'match' | 'check' | 'x' | 'remove') { Doc.setDocFilter(container, key, value, modifiers); }); -- cgit v1.2.3-70-g09d2 From 9d6c7f8100de3a952d20ad41ab20872737cb909e Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 27 Aug 2023 20:25:55 -0400 Subject: lots of cleanup to streamline import orderings (ie packages should not mutually import each other directly or via a chain). change raiseWhenDragged to be keepZWhenDragged and got rid of system wide default. --- src/Utils.ts | 2 +- src/client/DocServer.ts | 15 ++- src/client/apis/youtube/YoutubeBox.tsx | 6 +- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 11 +- src/client/util/DictationManager.ts | 2 +- src/client/util/DocumentManager.ts | 22 +++- src/client/util/DragManager.ts | 21 +--- src/client/util/DropConverter.ts | 14 +-- src/client/util/GroupManager.tsx | 4 +- src/client/util/GroupMemberView.tsx | 2 +- src/client/util/SearchUtil.ts | 104 ++++++++++++++- src/client/util/SettingsManager.tsx | 89 ++++++------- src/client/util/SharingManager.tsx | 10 +- src/client/util/reportManager/ReportManager.tsx | 4 +- src/client/views/ContextMenuItem.tsx | 6 +- src/client/views/DocumentDecorations.tsx | 11 +- src/client/views/EditableView.tsx | 1 - src/client/views/FilterPanel.tsx | 23 ++-- src/client/views/LightboxView.tsx | 11 +- src/client/views/Main.tsx | 6 +- src/client/views/MainView.tsx | 38 +++--- src/client/views/PropertiesView.tsx | 20 +-- src/client/views/SidebarAnnos.tsx | 4 +- src/client/views/StyleProvider.tsx | 15 ++- src/client/views/UndoStack.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 4 +- src/client/views/collections/CollectionMenu.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 4 +- src/client/views/collections/TabDocView.tsx | 13 +- src/client/views/collections/TreeSort.ts | 6 + src/client/views/collections/TreeView.tsx | 11 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/linking/LinkPopup.tsx | 3 +- .../views/newlightbox/ButtonMenu/ButtonMenu.tsx | 19 ++- src/client/views/newlightbox/NewLightboxView.tsx | 139 ++++++++++----------- src/client/views/nodes/DocumentView.tsx | 16 ++- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 17 +-- src/client/views/nodes/LinkDocPreview.tsx | 6 +- src/client/views/nodes/LoadingBox.tsx | 6 +- src/client/views/nodes/ScreenshotBox.tsx | 6 +- src/client/views/nodes/VideoBox.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 24 ++-- .../views/nodes/generativeFill/GenerativeFill.tsx | 33 +++-- src/client/views/nodes/trails/PresBox.tsx | 4 +- src/client/views/pdf/AnchorMenu.tsx | 15 +-- src/client/views/search/IconBar.tsx | 28 +++-- src/client/views/search/SearchBox.tsx | 110 +--------------- src/client/views/topbar/TopBar.tsx | 16 ++- src/client/views/webcam/DashWebRTCVideo.tsx | 6 +- src/fields/Doc.ts | 26 +++- src/fields/RichTextUtils.ts | 10 +- 52 files changed, 492 insertions(+), 494 deletions(-) create mode 100644 src/client/views/collections/TreeSort.ts (limited to 'src/fields') diff --git a/src/Utils.ts b/src/Utils.ts index 7f83ab8f5..9a94694a2 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -3,10 +3,10 @@ import v5 = require('uuid/v5'); import { ColorState } from 'react-color'; import * as rp from 'request-promise'; import { Socket } from 'socket.io'; +import { DocumentType } from './client/documents/DocumentTypes'; import { Colors } from './client/views/global/globalEnums'; import { Message } from './server/Message'; import Color = require('color'); -import { DocumentType } from './client/documents/DocumentTypes'; export namespace Utils { export let CLICK_TIME = 300; diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 53c7b857a..5fdea131b 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,6 +1,5 @@ import { runInAction } from 'mobx'; import * as rp from 'request-promise'; -import * as io from 'socket.io-client'; import { Doc, DocListCast, Opt } from '../fields/Doc'; import { UpdatingFromServer } from '../fields/DocSymbols'; import { FieldLoader } from '../fields/FieldLoader'; @@ -8,13 +7,13 @@ import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols'; import { ObjectField } from '../fields/ObjectField'; import { RefField } from '../fields/RefField'; import { DocCast, StrCast } from '../fields/Types'; -import MobileInkOverlay from '../mobile/MobileInkOverlay'; +//import MobileInkOverlay from '../mobile/MobileInkOverlay'; import { emptyFunction, Utils } from '../Utils'; import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message'; import { DocumentType } from './documents/DocumentTypes'; import { LinkManager } from './util/LinkManager'; import { SerializationHelper } from './util/SerializationHelper'; -import { GestureOverlay } from './views/GestureOverlay'; +//import { GestureOverlay } from './views/GestureOverlay'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -189,17 +188,17 @@ export namespace DocServer { // mobile ink overlay socket events to communicate between mobile view and desktop view _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => { - MobileInkOverlay.Instance.drawStroke(content); + // MobileInkOverlay.Instance.drawStroke(content); }); _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => { - GestureOverlay.Instance.enableMobileInkOverlay(content); - MobileInkOverlay.Instance.initMobileInkOverlay(content); + //GestureOverlay.Instance.enableMobileInkOverlay(content); + // MobileInkOverlay.Instance.initMobileInkOverlay(content); }); _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => { - MobileInkOverlay.Instance.updatePosition(content); + // MobileInkOverlay.Instance.updatePosition(content); }); _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => { - MobileInkOverlay.Instance.uploadDocument(content); + // MobileInkOverlay.Instance.uploadDocument(content); }); } diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 05879a247..2da9927c0 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -6,7 +6,7 @@ import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; -import { DocumentDecorations } from '../../views/DocumentDecorations'; +import { DocumentView } from '../../views/nodes/DocumentView'; import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; import '../../views/nodes/WebBox.scss'; import './YoutubeBox.scss'; @@ -355,9 +355,9 @@ export class YoutubeBox extends React.Component { ); - const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + const frozen = !this.props.isSelected() || DocumentView.Interacting; - const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? '-interactive' : ''); + const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentView.Interacting ? '-interactive' : ''); return ( <>
{content}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1186446e1..919958b24 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -19,7 +19,6 @@ import { aggregateBounds, OmitKeys, Utils } from '../../Utils'; import { YoutubeBox } from '../apis/youtube/YoutubeBox'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; -import { DocumentManager } from '../util/DocumentManager'; import { DragManager, dropActionType } from '../util/DragManager'; import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox'; import { FollowLinkScript } from '../util/LinkFollower'; @@ -34,12 +33,12 @@ import { ContextMenuProps } from '../views/ContextMenuItem'; import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; import { AudioBox } from '../views/nodes/AudioBox'; -import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { ColorBox } from '../views/nodes/ColorBox'; import { ComparisonBox } from '../views/nodes/ComparisonBox'; import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; import { EquationBox } from '../views/nodes/EquationBox'; import { FieldViewProps } from '../views/nodes/FieldView'; +import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox'; import { ImageBox } from '../views/nodes/ImageBox'; @@ -394,7 +393,7 @@ export class DocumentOptions { onPointerUp?: ScriptField; _forceActive?: BOOLt = new BoolInfo('flag to handle pointer events when not selected (or otherwise active)'); _dragOnlyWithinContainer?: BOOLt = new BoolInfo('whether the document should remain in its collection when someone tries to drag and drop it elsewhere'); - _raiseWhenDragged?: BOOLt = new BoolInfo('whether a document is brought to front when dragged.'); + _keepZWhenDragged?: BOOLt = new BoolInfo('whether a document should keep its z-order when dragged.'); childDragAction?: DROPt = new DAInfo('what should happen to the child documents when they are dragged from the collection'); dropConverter?: ScriptField; // script to run when documents are dropped on this Document. dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); @@ -1371,7 +1370,7 @@ export namespace DocUtils { export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { - broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1)); + broadcastEvent && runInAction(() => (Doc.RecordingEvent = Doc.RecordingEvent + 1)); return DocUtils.ActiveRecordings.map(audio => { const sourceDoc = getSourceDoc(); return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' }); @@ -1380,7 +1379,6 @@ export namespace DocUtils { export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) { if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; - const sv = DocumentManager.Instance.getDocumentView(source); if (target.doc === Doc.UserDoc()) return undefined; const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { @@ -1840,8 +1838,6 @@ export namespace DocUtils { } if (overwriteDoc) { Doc.removeCurrentlyLoading(overwriteDoc); - // loading doc icons are just labels. so any icon views of loading docs need to be replaced with the proper icon view. - DocumentManager.Instance.getAllDocumentViews(overwriteDoc).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())); } generatedDocuments.push(doc); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ea995e4af..d52e389d6 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -18,7 +18,6 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { TreeViewType } from "../views/collections/CollectionTreeView"; import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; -import { MainView } from "../views/MainView"; import { OpenWhere } from "../views/nodes/DocumentView"; import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox"; import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; @@ -621,9 +620,9 @@ export class CurrentUserUtils { static freeTools(): Button[] { return [ - { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform - { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform - { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform + { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { title: "Z order", icon: "z", toolTip: "Keep Z order on Drag", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: '{ return toggleRaiseOnDrag(_readOnly_);}'}}, // Only when floating document is selected in freeform ] } static viewTools(): Button[] { @@ -841,7 +840,6 @@ export class CurrentUserUtils { doc.isSystem ?? (doc.isSystem = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); Doc.noviceMode ?? (Doc.noviceMode = true); - doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); doc._showLabel ?? (doc._showLabel = true); doc.textAlign ?? (doc.textAlign = "left"); doc.activeTool = InkTool.None; @@ -996,8 +994,5 @@ export class CurrentUserUtils { ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); -ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); -ScriptingGlobals.add(function openPresentation(pres:Doc) { return MainView.Instance.openPresentation(pres); }, "creates a new presentation when called"); -ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); \ No newline at end of file diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 717473aa1..0fd7e840c 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,7 +11,7 @@ import { Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DictationOverlay } from '../views/DictationOverlay'; -import { DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { DocumentView, OpenWhere } from '../views/nodes/DocumentView'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 5b627c2f3..c2827dac7 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, ObservableSet } from 'mobx'; +import { action, computed, observable, ObservableSet, observe, reaction } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; @@ -23,7 +23,6 @@ export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) @observable _documentViews = new Set(); @observable public LinkAnchorBoxViews: DocumentView[] = []; - @observable public RecordingEvent = 0; @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = []; @computed public get DocumentViews() { return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath))); @@ -41,7 +40,22 @@ export class DocumentManager { } //private constructor so no other class can create a nodemanager - private constructor() {} + private constructor() { + if (!Doc.CurrentlyLoading) Doc.CurrentlyLoading = []; + observe(Doc.CurrentlyLoading, change => { + // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become + switch (change.type as any) { + case 'update': + break; + case 'remove': + // DocumentManager.Instance.getAllDocumentViews(change as any).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())); + break; + case 'splice': + (change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()))); + break; + } + }); + } private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => { @@ -310,7 +324,7 @@ export class DocumentManager { if (viewSpec && docView) { if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options); PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500); - Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec]: docView.rootDoc, undefined, options.effect); + Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect); if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc); if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden; if (options.effect) docView.rootDoc[Animation] = options.effect; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 489c9df4a..05da5ebed 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -61,12 +61,6 @@ export namespace DragManager { export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void>; export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; - export function GetRaiseWhenDragged() { - return BoolCast(Doc.UserDoc()._raiseWhenDragged); - } - export function SetRaiseWhenDragged(val: boolean) { - Doc.UserDoc()._raiseWhenDragged = val; - } export function Root() { const root = document.getElementById('root'); if (!root) { @@ -605,18 +599,9 @@ export namespace DragManager { } } -ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) { +ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { if (readOnly) { - if (SelectionManager.Views().length) - return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged) - ? Colors.MEDIUM_BLUE - : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false) - ? 'transparent' - : DragManager.GetRaiseWhenDragged() - ? Colors.MEDIUM_BLUE_ALT - : Colors.LIGHT_BLUE; - return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE_ALT : 'transparent'; + return SelectionManager.Views().some(dv => dv.rootDoc.keepZWhenDragged); } - if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false))); - else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); + SelectionManager.Views().map(dv => (dv.rootDoc.keepZWhenDragged = !dv.rootDoc.keepZWhenDragged)); }); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index f235be192..dbdf580cd 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,15 +1,15 @@ -import { DragManager } from './DragManager'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { DocumentType } from '../documents/DocumentTypes'; import { ObjectField } from '../../fields/ObjectField'; -import { StrCast, Cast } from '../../fields/Types'; -import { Docs } from '../documents/Documents'; -import { ScriptField, ComputedField } from '../../fields/ScriptField'; import { RichTextField } from '../../fields/RichTextField'; -import { ImageField } from '../../fields/URLField'; -import { ScriptingGlobals } from './ScriptingGlobals'; import { listSpec } from '../../fields/Schema'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, StrCast } from '../../fields/Types'; +import { ImageField } from '../../fields/URLField'; +import { Docs } from '../documents/Documents'; +import { DocumentType } from '../documents/DocumentTypes'; import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox'; +import { DragManager } from './DragManager'; +import { ScriptingGlobals } from './ScriptingGlobals'; export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined, templateField: string = '') { if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index c79894032..8973306bf 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -282,7 +282,7 @@ export class GroupManager extends React.Component<{}> { */ private get groupCreationModal() { const contents = ( -
+

New Group @@ -367,7 +367,7 @@ export class GroupManager extends React.Component<{}> { const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; return ( -

+
{this.groupCreationModal} {this.currentGroup ? (this.currentGroup = undefined))} /> : null}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index 535d8ccc2..7de0f336f 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -29,7 +29,7 @@ export class GroupMemberView extends React.Component { const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group); return !this.props.group ? null : ( -
+
, query: string) { + const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; + const blockedKeys = [ + 'x', + 'y', + 'proto', + 'width', + 'layout_autoHeight', + 'acl-Override', + 'acl-Guest', + 'embedContainer', + 'zIndex', + 'height', + 'text_scrollHeight', + 'text_height', + 'cloneFieldFilter', + 'isDataDoc', + 'text_annotations', + 'dragFactory_count', + 'text_noTemplate', + 'proto_embeddings', + 'isSystem', + 'layout_fieldKey', + 'isBaseProto', + 'xMargin', + 'yMargin', + 'links', + 'layout', + 'layout_keyValue', + 'layout_fitWidth', + 'type_collection', + 'title_custom', + 'freeform_panX', + 'freeform_panY', + 'freeform_scale', + ]; + query = query.toLowerCase(); + + const results = new Map(); + if (rootDoc) { + const docs = DocListCast(rootDoc[Doc.LayoutFieldKey(rootDoc)]); + const docIDs: String[] = []; + SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { + const dtype = StrCast(doc.type) as DocumentType; + if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) { + const hlights = new Set(); + SearchUtil.documentKeys(doc).forEach( + key => + Field.toString(doc[key] as Field) + .toLowerCase() + .includes(query) && hlights.add(key) + ); + blockedKeys.forEach(key => hlights.delete(key)); + + if (Array.from(hlights.keys()).length > 0) { + results.set(doc, Array.from(hlights.keys())); + } + } + docIDs.push(doc[Id]); + }); + } + return results; + } + /** + * @param {Doc} doc - doc for which keys are returned + * + * This method returns a list of a document doc's keys. + */ + export function documentKeys(doc: Doc) { + const keys: { [key: string]: boolean } = {}; + Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))); + return Array.from(Object.keys(keys)); + } + + /** + * @param {Doc[]} docs - docs to be searched through recursively + * @param {number, Doc => void} func - function to be called on each doc + * + * This method iterates through an array of docs and all docs within those docs, calling + * the function func on each doc. + */ + export function foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) { + let newarray: Doc[] = []; + var depth = 0; + const visited: Doc[] = []; + while (docs.length > 0) { + newarray = []; + docs.filter(d => d && !visited.includes(d)).forEach(d => { + visited.push(d); + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const data = d[annos ? fieldKey + '_annotations' : fieldKey]; + data && newarray.push(...DocListCast(data)); + const sidebar = d[fieldKey + '_sidebar']; + sidebar && newarray.push(...DocListCast(sidebar)); + func(depth, d); + }); + docs = newarray; + depth++; + } + } export interface IdSearchResult { ids: string[]; lines: string[][]; diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index bb370e1a4..720badd40 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -13,7 +13,6 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; import { undoBatch } from './UndoManager'; @@ -67,15 +66,15 @@ export class SettingsManager extends React.Component<{}> { } }; - @computed get userColor() { + @computed public static get userColor() { return StrCast(Doc.UserDoc().userColor); } - @computed get userVariantColor() { + @computed public static get userVariantColor() { return StrCast(Doc.UserDoc().userVariantColor); } - @computed get userBackgroundColor() { + @computed public static get userBackgroundColor() { return StrCast(Doc.UserDoc().userBackgroundColor); } @@ -150,35 +149,35 @@ export class SettingsManager extends React.Component<{}> { val: scheme, }))} dropdownType={DropdownType.SELECT} - color={this.userColor} + color={SettingsManager.userColor} fillWidth /> {userTheme === ColorScheme.Custom && ( } - selectedColor={this.userColor} + selectedColor={SettingsManager.userColor} setSelectedColor={this.switchUserColor} setFinalColor={this.switchUserColor} /> } - selectedColor={this.userBackgroundColor} + selectedColor={SettingsManager.userBackgroundColor} setSelectedColor={this.switchUserBackgroundColor} setFinalColor={this.switchUserBackgroundColor} /> } - selectedColor={this.userVariantColor} + selectedColor={SettingsManager.userVariantColor} setSelectedColor={this.switchUserVariantColor} setFinalColor={this.switchUserVariantColor} /> @@ -198,7 +197,7 @@ export class SettingsManager extends React.Component<{}> { onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} toggleStatus={Doc.UserDoc().layout_showTitle !== undefined} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> { onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} - toggleStatus={FontIconBox.GetShowLabels()} + onClick={e => Doc.SetShowIconLabels(!Doc.GetShowIconLabels())} + toggleStatus={Doc.GetShowIconLabels()} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} - toggleStatus={FontIconBox.GetRecognizeGestures()} + onClick={e => Doc.SetRecognizeGestures(!Doc.GetRecognizeGestures())} + toggleStatus={Doc.GetRecognizeGestures()} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> { onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> { onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> { onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)} toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} />
); @@ -284,7 +283,7 @@ export class SettingsManager extends React.Component<{}> {
{/* */} - {}} /> + {}} /> { return { @@ -301,7 +300,7 @@ export class SettingsManager extends React.Component<{}> { setSelectedVal={val => { this.changeFontFamily(val as string); }} - color={this.userColor} + color={SettingsManager.userColor} fillWidth /> @@ -329,12 +328,12 @@ export class SettingsManager extends React.Component<{}> { @computed get passwordContent() { return (
- this.changeVal(val as string, 'curr')} fillWidth password /> - this.changeVal(val as string, 'new')} fillWidth password /> - this.changeVal(val as string, 'conf')} fillWidth password /> + this.changeVal(val as string, 'curr')} fillWidth password /> + this.changeVal(val as string, 'new')} fillWidth password /> + this.changeVal(val as string, 'conf')} fillWidth password /> {!this.passwordResultText ? null :
{this.passwordResultText}
} -
); } @@ -394,10 +393,10 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement="bottom-start" - color={this.userColor} + color={SettingsManager.userColor} fillWidth /> - +
Freeform Navigation @@ -422,15 +421,21 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement="bottom-start" - color={this.userColor} + color={SettingsManager.userColor} />
Permissions
-
@@ -449,7 +454,7 @@ export class SettingsManager extends React.Component<{}> { ]; return (
-
+
{tabs.map(tab => { const isActive = this.activeTab === tab.title; @@ -457,8 +462,8 @@ export class SettingsManager extends React.Component<{}> {
(this.activeTab = tab.title))}> @@ -469,19 +474,19 @@ export class SettingsManager extends React.Component<{}> {
-
{DashVersion}
-
+
{DashVersion}
+
{Doc.CurrentUserEmail}
-
-
-
+
{tabs.map(tab => (
{tab.ele} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 81ddeb9e3..9a9097bf7 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -18,10 +18,10 @@ import { DictationOverlay } from '../views/DictationOverlay'; import { MainViewModal } from '../views/MainViewModal'; import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; -import { SearchBox } from '../views/search/SearchBox'; import { DocumentManager } from './DocumentManager'; import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; +import { SearchUtil } from './SearchUtil'; import { SelectionManager } from './SelectionManager'; import { SettingsManager } from './SettingsManager'; import './SharingManager.scss'; @@ -446,7 +446,7 @@ export class SharingManager extends React.Component<{}> { if (this.myDocAcls) { const newDocs: Doc[] = []; - SearchBox.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc)); + SearchUtil.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc)); docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin); } @@ -527,10 +527,10 @@ export class SharingManager extends React.Component<{}> { const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : ( -
+
{StrCast(group.title)}
  - {group instanceof Doc ? } size={Size.XSMALL} color={SettingsManager.Instance.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null} + {group instanceof Doc ? } size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null}
{admin || this.myDocAcls ? ( diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index c194ede32..a2f9de9ab 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -1,13 +1,14 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, IconButton, Size, Type, isDark } from 'browndash-components'; +import { Button, IconButton, isDark, Size, Type } from 'browndash-components'; import { action, computed, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { FaBug, FaCamera, FaStamp } from 'react-icons/fa'; +import { FaBug, FaCamera } from 'react-icons/fa'; import { Doc, DocListCast } from '../../../fields/Doc'; import { AclAdmin, DashVersion } from '../../../fields/DocSymbols'; import { StrCast } from '../../../fields/Types'; import { GetEffectiveAcl } from '../../../fields/util'; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { PingManager } from '../../util/PingManager'; import { ReportManager } from '../../util/reportManager/ReportManager'; @@ -15,13 +16,12 @@ import { ServerStats } from '../../util/ServerStats'; import { SettingsManager } from '../../util/SettingsManager'; import { SharingManager } from '../../util/SharingManager'; import { UndoManager } from '../../util/UndoManager'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; import { ContextMenu } from '../ContextMenu'; import { DashboardView } from '../DashboardView'; -import { MainView } from '../MainView'; -import { CollectionDockingView } from '../collections/CollectionDockingView'; import { Colors } from '../global/globalEnums'; +import { DocumentView } from '../nodes/DocumentView'; import './TopBar.scss'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; /** * ABOUT: This is the topbar in Dash, which included the current Dashboard as well as access to information on the user @@ -43,7 +43,7 @@ export class TopBar extends React.Component { return StrCast(Doc.UserDoc().userVariantColor, Colors.MEDIUM_BLUE); } @computed get backgroundColor() { - return PingManager.Instance.IsBeating ? SettingsManager.Instance.userBackgroundColor : Colors.MEDIUM_GRAY; + return PingManager.Instance.IsBeating ? SettingsManager.userBackgroundColor : Colors.MEDIUM_GRAY; } @observable happyHeart: boolean = PingManager.Instance.IsBeating; @@ -76,9 +76,7 @@ export class TopBar extends React.Component { dash
)} - {Doc.ActiveDashboard && ( -
); } diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index 02e44a793..524492226 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -6,8 +6,8 @@ import { observer } from 'mobx-react'; import { Doc } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import '../../views/nodes/WebBox.scss'; -import { DocumentDecorations } from '../DocumentDecorations'; import { CollectionFreeFormDocumentViewProps } from '../nodes/CollectionFreeFormDocumentView'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './DashWebRTCVideo.scss'; import { hangup, initialize, refreshVideos } from './WebCamLogic'; @@ -71,8 +71,8 @@ export class DashWebRTCVideo extends React.Component ); - const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? '-interactive' : ''); + const frozen = !this.props.isSelected() || DocumentView.Interacting; + const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentView.Interacting ? '-interactive' : ''); return ( <> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4ad38e7fc..f17e10d9e 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -47,7 +47,7 @@ import { FieldId, RefField } from './RefField'; import { RichTextField } from './RichTextField'; import { listSpec } from './Schema'; import { ComputedField, ScriptField } from './ScriptField'; -import { Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; +import { BoolCast, Cast, DocCast, FieldValue, NumCast, StrCast, ToConstructor } from './Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField } from './URLField'; import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions } from './util'; import JSZip = require('jszip'); @@ -156,7 +156,26 @@ export function updateCachedAcls(doc: Doc) { @scriptingGlobal @Deserializable('Doc', updateCachedAcls, ['id']) export class Doc extends RefField { - @observable public static CurrentlyLoading: Doc[]; + @observable public static RecordingEvent = 0; + + // this isn't really used at the moment, but is intended to indicate whether ink stroke are passed through a gesture recognizer + static GetRecognizeGestures() { + return BoolCast(Doc.UserDoc()._recognizeGestures); + } + static SetRecognizeGestures(show: boolean) { + Doc.UserDoc()._recognizeGestures = show; + } + + // + // This controls whether fontIconButtons will display labels under their icons or not + // + static GetShowIconLabels() { + return BoolCast(Doc.UserDoc()._showLabel); + } + static SetShowIconLabels(show: boolean) { + Doc.UserDoc()._showLabel = show; + } + @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor // removes from currently loading display @action public static removeCurrentlyLoading(doc: Doc) { @@ -169,9 +188,6 @@ export class Doc extends RefField { // adds doc to currently loading display @action public static addCurrentlyLoading(doc: Doc) { - if (!Doc.CurrentlyLoading) { - Doc.CurrentlyLoading = []; - } if (Doc.CurrentlyLoading.indexOf(doc) === -1) { Doc.CurrentlyLoading.push(doc); } diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index 24cd078f2..5ecf25e08 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -2,20 +2,20 @@ import { AssertionError } from 'assert'; import { docs_v1 } from 'googleapis'; import { Fragment, Mark, Node } from 'prosemirror-model'; import { sinkListItem } from 'prosemirror-schema-list'; -import { Utils, DashColor } from '../Utils'; -import { Docs, DocUtils } from '../client/documents/Documents'; -import { schema } from '../client/views/nodes/formattedText/schema_rts'; +import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; +import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; import { GooglePhotos } from '../client/apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from '../client/DocServer'; +import { Docs, DocUtils } from '../client/documents/Documents'; import { Networking } from '../client/Network'; import { FormattedTextBox } from '../client/views/nodes/formattedText/FormattedTextBox'; +import { schema } from '../client/views/nodes/formattedText/schema_rts'; +import { DashColor, Utils } from '../Utils'; import { Doc, Opt } from './Doc'; import { Id } from './FieldSymbols'; import { RichTextField } from './RichTextField'; import { Cast, StrCast } from './Types'; import Color = require('color'); -import { EditorState, TextSelection, Transaction } from 'prosemirror-state'; -import { GoogleApiClientUtils } from '../client/apis/google_docs/GoogleApiClientUtils'; export namespace RichTextUtils { const delimiter = '\n'; -- cgit v1.2.3-70-g09d2 From 611992db3635123da39c64a98ff656a5d2f38f7a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 28 Aug 2023 14:12:05 -0400 Subject: fixed runtime error in sharing manager when sharing the dashboard --- src/client/util/SharingManager.tsx | 28 +++++++++++++++------------- src/fields/Doc.ts | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) (limited to 'src/fields') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 9a9097bf7..8d59426ec 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -378,15 +378,17 @@ export class SharingManager extends React.Component<{}> { } }); - const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); - TaskCompletionBox.popupX = left - 1.5 * width; - TaskCompletionBox.popupY = top - 1.5 * height; - TaskCompletionBox.textDisplayed = 'Document shared!'; - TaskCompletionBox.taskCompleted = true; - setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), - 2000 - ); + if (this.shareDocumentButtonRef.current) { + const { left, width, top, height } = this.shareDocumentButtonRef.current.getBoundingClientRect(); + TaskCompletionBox.popupX = left - 1.5 * width; + TaskCompletionBox.popupY = top - 1.5 * height; + TaskCompletionBox.textDisplayed = 'Document shared!'; + TaskCompletionBox.taskCompleted = true; + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); + } this.layoutDocAcls = false; this.selectedUsers = null; @@ -555,7 +557,7 @@ export class SharingManager extends React.Component<{}> { background: SettingsManager.userBackgroundColor, color: StrCast(Doc.UserDoc().userColor), }}> -

+

window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}> window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
@@ -563,10 +565,10 @@ export class SharingManager extends React.Component<{}> { {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}

-
-
{admin ? (
@@ -608,7 +610,7 @@ export class SharingManager extends React.Component<{}> {
-
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index f17e10d9e..ad0e548ed 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1001,7 +1001,7 @@ export namespace Doc { references.add(doc); return; } - const excludeLists = doc.title === 'My Recently Closed' || doc.title === 'My Header Bar' || doc.title === 'My Dashboards'; + const excludeLists = ['My Recently Closed', 'My Header Bar', 'My Dashboards'].includes(StrCast(doc.title)); if (system !== undefined && ((system && !Doc.IsSystem(doc)) || (!system && Doc.IsSystem(doc)))) return; references.add(doc); Object.keys(doc).forEach(key => { -- cgit v1.2.3-70-g09d2 From 7c218639c75e22e1270d4198fb940b439175deee Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 31 Aug 2023 11:50:14 -0400 Subject: reworked recording workspace UI and switched to recording window, not webcam --- src/client/util/CurrentUserUtils.ts | 58 +++--- src/client/util/DragManager.ts | 7 +- src/client/views/DashboardView.tsx | 8 +- .../collectionLinear/CollectionLinearView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 90 ++++----- .../views/nodes/RecordingBox/RecordingBox.tsx | 215 ++++++++++----------- src/client/views/nodes/ScreenshotBox.tsx | 3 + src/client/views/topbar/TopBar.tsx | 63 ++++-- src/fields/Doc.ts | 3 + 10 files changed, 234 insertions(+), 219 deletions(-) (limited to 'src/fields') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 51ef51508..04f1ff0d2 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -18,6 +18,7 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { TreeViewType } from "../views/collections/CollectionTreeView"; import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; +import { media_state } from "../views/nodes/AudioBox"; import { OpenWhere } from "../views/nodes/DocumentView"; import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox"; import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; @@ -51,7 +52,7 @@ interface Button { // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } - funcs?: { [key:string]: string }; + funcs?: { [key:string]: any}; subMenu?: Button[]; } @@ -591,7 +592,7 @@ export class CurrentUserUtils { static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, _dropPropertiesToRemove: new List([ "_layout_hideContextMenu"]), - _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts, + /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts, }) /// initializes the required buttons in the expanding button menu at the bottom of the Dash window @@ -693,7 +694,6 @@ export class CurrentUserUtils { { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } - static contextMenuTools():Button[] { return [ { btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, @@ -701,11 +701,11 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), - title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, + title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: '{ return toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, @@ -714,16 +714,8 @@ export class CurrentUserUtils { { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected - { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected - { title: "Video", icon: 'video', toolTip: "Dictate", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `getIsRecording()`}, ignoreClick: true, scripts: { onClick: 'return toggleRecording(_readOnly_)'}}, - { title: "StopRec", icon: "stop", toolTip: "Stop", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `!getIsRecording()`}, ignoreClick: true, scripts: { onClick: `return toggleRecording(_readOnly_)`}}, - { title: "Dropdown", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList, expertMode: false, funcs: {hidden: `renderDropdown()`, btnList: `getWorkspaceRecordings()`}, ignoreClick: true, scripts: { script: `toggleRecPlayback(value)`}}, - { title: "Play Rec",icon: "play", toolTip: "Play recording", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `!getIsWorkspaceRecPlaying()`}, ignoreClick: true, scripts: { onClick: `return playWorkspaceRec(getCurrentRecording())`}}, - { title: "Pause Rec",icon: "pause", toolTip: "Pause recording", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `!getIsWorkspaceRecPaused()`}, ignoreClick: true, scripts: { onClick: `return pauseWorkspaceRec(getCurrentRecording())`}}, - { title: "Stop Rec", icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `!getIsRecPlayback()`}, ignoreClick: true, scripts: { onClick: `return closeWorkspaceRec(getCurrentRecording())`}}, - { title: "Add doc", icon: "down", toolTip: "add to doc", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `!getIsRecPlayback()`}, ignoreClick: true, scripts: { onClick: `addRectoWorkspace(getCurrentRecording())`}}, - { title: "Delete Rec", icon: "trash", toolTip: "delete selected recording", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {hidden: `!getIsRecPlayback()`}, ignoreClick: true, scripts: { onClick: `removeWorkspaceRec(getCurrentRecording())`}} - ]; + { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected + ]; } /// initializes a context menu button for the top bar context menu @@ -749,19 +741,17 @@ export class CurrentUserUtils { if (Doc.UserDoc().currentRecording) { Doc.RemFromMyOverlay(DocCast(Doc.UserDoc().currentRecording)); } - Doc.UserDoc().isRecording = false; - Doc.UserDoc().isRecPlayback = false; + Doc.UserDoc().workspaceRecordingState = undefined; + Doc.UserDoc().workspaceReplayingState = undefined; Doc.UserDoc().currentRecording = undefined; - Doc.UserDoc().isPlaybackPlaying = false; - Doc.UserDoc().isWorkspaceRecPlaying = false; - Doc.UserDoc().isWorkspaceRecPaused = false; + Doc.UserDoc().workspaceRecordingState = undefined; if (!subMenu) { // button does not have a sub menu return this.setupContextMenuButton(params, menuBtnDoc); } // linear view const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List(['width', "linearView_IsOpen"]), childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true, - linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton}; + linearView_SubMenu: true, linearView_Expandable: true}; const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) ); const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList; @@ -773,11 +763,34 @@ export class CurrentUserUtils { /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; + const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: true, _height: 35 }; const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) ); return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } + /// Initializes all the default buttons for the top bar context menu + static setupTopbarButtons(doc: Doc, field="myTopBarBtns") { + const dockedBtns = DocCast(doc[field]); + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) => + DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? + CurrentUserUtils.createToolButton(opts), scripts, funcs); + + const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet + { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}}, + { opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`}}, + { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}}, + { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}}, + { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}}, + { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}}, + { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}} + ]; + const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs)); + const dockBtnsReqdOpts:DocumentOptions = { + title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move', + childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true + }; + return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); + } /// collection of documents rendered in the overlay layer above all tabs and other UI static setupOverlays(doc: Doc, field = "myOverlayDocs") { @@ -891,6 +904,7 @@ export class CurrentUserUtils { this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box) this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected + this.setupTopbarButtons(doc); this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 065f17139..6c0641943 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -243,13 +243,10 @@ export namespace DragManager { return dropDoc; }; const finishDrag = async (e: DragCompleteEvent) => { - Doc.UserDoc().isAddRecToDocMode = false; Doc.RemFromMyOverlay(DocCast(Doc.UserDoc().currentRecording)); Doc.UserDoc().currentRecording = undefined; - Doc.UserDoc().isRecPlayback = false; - Doc.UserDoc().isWorkspaceRecPlaying = false; - Doc.UserDoc().isWorkspaceRecPaused = false; - Doc.UserDoc().isAddRecToDocMode = false; + Doc.UserDoc().isWorkspaceReplaying = false; + Doc.UserDoc().workspaceRecordingState = undefined; Cast(Doc.UserDoc().workspaceRecordings, listSpec(Doc), null)?.splice(recordingIndex, 1); const docDragData = e.docDragData; dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 4387c6e96..3ef6c0814 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -18,7 +18,7 @@ import { CollectionViewType } from '../documents/DocumentTypes'; import { HistoryUtil } from '../util/History'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SharingManager } from '../util/SharingManager'; -import { undoBatch } from '../util/UndoManager'; +import { undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionView } from './collections/CollectionView'; import { ContextMenu } from './ContextMenu'; @@ -493,6 +493,8 @@ ScriptingGlobals.add(function resetDashboard(dashboard: Doc) { ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { DashboardView.openDashboard(Doc.MakeEmbedding(dashboard)); }, 'adds Dashboard to set of Dashboards'); -ScriptingGlobals.add(function snapshotDashboard() { - DashboardView.snapshotDashboard(); +ScriptingGlobals.add(async function snapshotDashboard() { + const batch = UndoManager.StartBatch('snapshot'); + await DashboardView.snapshotDashboard(); + batch.end(); }, 'creates a snapshot copy of a dashboard'); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 3481d5130..0cf7d4411 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -229,8 +229,8 @@ export class CollectionLinearView extends CollectionSubView() {
{ <> - {menuOpener} - {!this.layoutDoc.linearView_IsOpen ? null : ( + {!this.layoutDoc.linearView_Expandable ? null :menuOpener} + {!this.layoutDoc.linearView_IsOpen && !this.layoutDoc.linearView_alwaysOpen ? null : (
Doc.Zip(this.props.Document) }); (this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) { diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index bb2069cc9..8ef9cd792 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -180,55 +180,45 @@ export class FontIconBox extends DocComponent() { * Dropdown list */ @computed get dropdownListButton() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const script = ScriptCast(this.rootDoc.script); let noviceList: string[] = []; let text: string | undefined; - let dropdown = true; let getStyle: (val: string) => any = () => {}; let icon: IconProp = 'caret-down'; - let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView'); - try { - if (isViewDropdown) { - const selectedDocs: Doc[] = SelectionManager.Docs(); - const selected = SelectionManager.Docs().lastElement(); - if (selected) { - if (StrCast(selected.type) === DocumentType.COL) { - text = StrCast(selected._type_collection); + const isViewDropdown = script?.script.originalScript.startsWith('setView'); + if (isViewDropdown) { + const selected = SelectionManager.Docs(); + if (selected.lastElement()) { + if (StrCast(selected.lastElement().type) === DocumentType.COL) { + text = StrCast(selected.lastElement()._type_collection); + } else { + if (selected.length > 1) { + text = selected.length + ' selected'; } else { - dropdown = false; - if (selectedDocs.length > 1) { - text = selectedDocs.length + ' selected'; - } else { - text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType); - icon = Doc.toIcon(selected); - } - return ( - } - text={text} - type={Type.TERT} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - popup={} - fillWidth - /> - ); + text = Utils.cleanDocumentType(StrCast(selected.lastElement().type) as DocumentType); + icon = Doc.toIcon(selected.lastElement()); } - } else { - dropdown = false; - return
) : null; } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ad0e548ed..2a1dfbfc7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -220,6 +220,9 @@ export class Doc extends RefField { public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } + public static get MyTopBarBtns() { + return DocCast(Doc.UserDoc().myTopBarBtns); + } public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } -- cgit v1.2.3-70-g09d2 From dcb301a78563994564663a31cebfd5ef38d135f0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 2 Sep 2023 10:50:08 -0400 Subject: from last --- src/fields/DocSymbols.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/fields') diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index 856c377fa..5b743f710 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -24,4 +24,4 @@ export const Initializing = Symbol('DocInitializing'); export const ForceServerWrite = Symbol('DocForceServerWrite'); export const CachedUpdates = Symbol('DocCachedUpdates'); -export const DashVersion = 'v0.5.7'; +export const DashVersion = 'v0.5.99'; -- cgit v1.2.3-70-g09d2 From dc92e167391988b63e3ff15e67bcfad6df21c044 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 6 Sep 2023 13:53:32 -0400 Subject: fixed up audio annotation to add dictation to sidebar note. --- src/client/util/SearchUtil.ts | 4 +- src/client/views/StyleProvider.tsx | 2 +- src/client/views/global/globalScripts.ts | 4 +- src/client/views/nodes/DocumentView.tsx | 33 +++++++++++------ .../views/nodes/formattedText/FormattedTextBox.tsx | 43 +++++++++++++++------- src/client/views/pdf/AnchorMenu.tsx | 18 ++++----- src/fields/Doc.ts | 2 + src/fields/DocSymbols.ts | 1 + 8 files changed, 67 insertions(+), 40 deletions(-) (limited to 'src/fields') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 64aa7ba9b..560d6b30f 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -57,8 +57,8 @@ export namespace SearchUtil { const hlights = new Set(); SearchUtil.documentKeys(doc).forEach( key => - Field.toString(doc[key] as Field) - .toLowerCase() + (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field)) + .toLowerCase() // .includes(query) && hlights.add(key) ); blockedKeys.forEach(key => hlights.delete(key)); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a9e4ac17d..cdf104a18 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -295,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped'); const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length; - if (!doc || props?.renderDepth === -1 || (!audioAnnosCount(doc) && audioAnnoState(doc) === 'stopped')) return null; + if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null; const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' }; return ( {StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}
}> diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 38bf1042d..9b9cc6d24 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -182,8 +182,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); // prettier-ignore const attrs:attrfuncs[] = [ - ['dictation', { checkResult: () => textView?._recording ? true:false, - toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }], + ['dictation', { checkResult: () => textView?._recordingDictation ? true:false, + toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }], ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ab99f3c6d..dcb2d9d51 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; -import { AclPrivate, Animation, DocData, Width } from '../../../fields/DocSymbols'; +import { AclPrivate, Animation, AudioPlay, DocData, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -52,6 +52,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); +import { isThisTypeNode } from 'typescript'; const { Howl } = require('howler'); interface Window { @@ -1054,7 +1055,7 @@ export class DocumentViewInternal extends DocComponent (dataDoc.audioAnnoState = 'recording')); + //runInAction(() => (dataDoc.audioAnnoState = 'recording')); recorder.start(); const stopFunc = () => { recorder.stop(); @@ -1071,16 +1072,24 @@ export class DocumentViewInternal extends DocComponent (self.dataDoc.audioAnnoState = 'stopped')), - }); - this.dataDoc.audioAnnoState = 'playing'; + if (anno instanceof AudioField) { + switch (audioAnnoState) { + case 'stopped': + this.dataDoc[AudioPlay] = new Howl({ + src: [anno.url.href], + format: ['mp3'], + autoplay: true, + loop: false, + volume: 0.5, + onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')), + }); + this.dataDoc.audioAnnoState = 'playing'; + break; + case 'playing': + this.dataDoc[AudioPlay]?.stop(); + this.dataDoc.audioAnnoState = 'stopped'; + break; + } } }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c9481482f..58b824159 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -153,10 +153,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { !this.layoutDoc.layout_showSidebar && this.toggleSidebar(); const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true); + setTimeout(() => { const target = this._sidebarRef.current?.anchorMenuClick(anchor); if (target) { anchor.followLinkAudio = true; - DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target)); + let stopFunc: any; + Doc.GetProto(target).mediaState = 'recording'; + Doc.GetProto(target).audioAnnoState = 'recording'; + DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop)); + let reactionDisposer = reaction( + () => target.mediaState, + action(dictation => { + if (!dictation) { + Doc.GetProto(target).audioAnnoState = 'stopped'; + stopFunc(); + reactionDisposer(); + } + }) + ); target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`); } }); @@ -948,14 +962,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (this._editorView && this._recording) { + if (this._editorView && this._recordingDictation) { this.stopDictation(true); this._break = true; const state = this._editorView.state; const to = state.selection.to; const updated = TextSelection.create(state.doc, to, to); this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to)); - if (this._recording) { + if (this._recordingDictation) { this.recordDictation(); } } @@ -1245,13 +1259,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._recording, + () => this._recordingDictation, () => { this.stopDictation(true); - this._recording && this.recordDictation(); - } + this._recordingDictation && this.recordDictation(); + }, + { fireImmediately: true } ); - if (this._recording) setTimeout(this.recordDictation); + if (this._recordingDictation) setTimeout(this.recordDictation); } var quickScroll: string | undefined = ''; this._disposers.scroll = reaction( @@ -1549,8 +1564,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent disposer?.()); this.endUndoTypingBatch(); @@ -1588,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent @@ -1899,7 +1914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this._recording = !this._recording)) + action(e => (this._recordingDictation = !this._recordingDictation)) ) }> diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index a52250145..85c1cce8c 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -132,7 +132,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip={'Click to Highlight'} onClick={this.highlightClicked} colorPicker={this.highlightColor} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> @@ -177,7 +177,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip="Summarize with AI" // onPointerDown={this.gptSummarize} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> )} {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( @@ -185,7 +185,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip="Click to Record Annotation" // onPointerDown={this.audioDown} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> )} { type={Type.PRIM} icon={} popup={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
@@ -201,7 +201,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip="Click/Drag to create cropped image" // onPointerDown={this.cropDown} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} />
)} @@ -213,7 +213,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip="Remove Link Anchor" // onPointerDown={this.Delete} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> )} {this.PinToPres !== returnFalse && ( @@ -221,7 +221,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip="Pin to Presentation" // onPointerDown={this.PinToPres} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> )} {this.ShowTargetTrail !== returnFalse && ( @@ -229,7 +229,7 @@ export class AnchorMenu extends AntimodeMenu { tooltip="Show Linked Trail" // onPointerDown={this.ShowTargetTrail} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> )} {this.IsTargetToggler !== returnFalse && ( @@ -240,7 +240,7 @@ export class AnchorMenu extends AntimodeMenu { toggleStatus={this.IsTargetToggler()} onClick={this.MakeTargetToggle} icon={} - color={StrCast(Doc.UserDoc().userColor)} + color={SettingsManager.userColor} /> )} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 2a1dfbfc7..dd3f11444 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -20,6 +20,7 @@ import { AclPrivate, AclReadonly, Animation, + AudioPlay, CachedUpdates, DirectLinks, DocAcl, @@ -349,6 +350,7 @@ export class Doc extends RefField { @observable public [DocAcl]: { [key: string]: symbol } = {}; @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout @observable public [DirectLinks] = new ObservableSet(); + @observable public [AudioPlay]: any; // meant to store sound object from Howl @observable public [Animation]: Opt; @observable public [Highlight]: boolean = false; static __Anim(Doc: Doc) { diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index 5b743f710..bb974ee74 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -3,6 +3,7 @@ export const Self = Symbol('DocSelf'); export const SelfProxy = Symbol('DocSelfProxy'); export const FieldKeys = Symbol('DocFieldKeys'); export const FieldTuples = Symbol('DocFieldTuples'); +export const AudioPlay = Symbol('DocAudioPlay'); export const Width = Symbol('DocWidth'); export const Height = Symbol('DocHeight'); export const Animation = Symbol('DocAnimation'); -- cgit v1.2.3-70-g09d2 From 507cdb40d5553c8a325ddb158b9a1f4e5672ac15 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 11 Sep 2023 22:35:42 -0400 Subject: moved edit on click script to developer. fixed highlight options for My text. enabled chromeHidden for all doc types and applied it to RTF text boxes. updated sliders in properties view to undo properly and to autorange, and cleaned up ode. --- package-lock.json | 30 ++-- package.json | 2 +- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesSection.tsx | 6 +- src/client/views/PropertiesView.scss | 3 +- src/client/views/PropertiesView.tsx | 186 ++++++++++----------- src/client/views/nodes/DocumentView.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 10 +- src/client/views/nodes/formattedText/marks_rts.ts | 2 +- src/fields/DocSymbols.ts | 2 +- 10 files changed, 115 insertions(+), 132 deletions(-) (limited to 'src/fields') diff --git a/package-lock.json b/package-lock.json index 6055e9d1e..d15959b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2852,9 +2852,9 @@ } }, "@floating-ui/dom": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.1.tgz", - "integrity": "sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.2.tgz", + "integrity": "sha512-6ArmenS6qJEWmwzczWyhvrXRdI/rI78poBcW0h/456+onlabit+2G+QxHx5xTOX60NBJQXjsCLFbW2CmsXpUog==", "requires": { "@floating-ui/core": "^1.4.1", "@floating-ui/utils": "^0.1.1" @@ -2869,9 +2869,9 @@ } }, "@floating-ui/utils": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.1.tgz", - "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.2.tgz", + "integrity": "sha512-ou3elfqG/hZsbmF4bxeJhPHIf3G2pm0ujc39hYEZrfVqt7Vk/Zji6CXc3W0pmYM8BW1g40U+akTl9DKZhFhInQ==" }, "@fortawesome/fontawesome-common-types": { "version": "6.4.0", @@ -6687,9 +6687,9 @@ } }, "browndash-components": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.25.tgz", - "integrity": "sha512-YgzUsfeUKwtaOLRYNDcxybZG2Ip7KSQV38VbUrgC+ycwOX/tzq9IWlfw2z4h/EVuVcq+ERCaBS4pb3mMU6pKGQ==", + "version": "0.1.35", + "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.35.tgz", + "integrity": "sha512-xA4ACEppnDe5ThnUZPn9vo4W/KeZJZT5EFWh2tM0IsH/ZfcOhJopGfV6ZoIHHpvS5f/zi6l7YJuWaxd+aK5arg==", "requires": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -10146,14 +10146,14 @@ } }, "caniuse-lite": { - "version": "1.0.30001529", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001529.tgz", - "integrity": "sha512-n2pUQYGAkrLG4QYj2desAh+NqsJpHbNmVZz87imptDdxLAtjxary7Df/psdfyDGmskJK/9Dt9cPnx5RZ3CU4Og==" + "version": "1.0.30001532", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz", + "integrity": "sha512-FbDFnNat3nMnrROzqrsg314zhqN5LGQ1kyyMk2opcrwGbVGpHRhgCWtAgD5YJUqNAiQ+dklreil/c3Qf1dfCTw==" }, "electron-to-chromium": { - "version": "1.4.513", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.513.tgz", - "integrity": "sha512-cOB0xcInjm+E5qIssHeXJ29BaUyWpMyFKT5RB3bsLENDheCja0wMkHJyiPl0NBE/VzDI7JDuNEQWhe6RitEUcw==" + "version": "1.4.515", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.515.tgz", + "integrity": "sha512-VTq6vjk3kCfG2qdzQRd/i9dIyVVm0dbtZIgFzrLgfB73mXDQT2HPKVRc1EoZcAVUv9XhXAu08DWqJuababdGGg==" } } }, diff --git a/package.json b/package.json index 9f0b91628..92ebf0b0f 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,7 @@ "body-parser": "^1.19.2", "bootstrap": "^4.6.1", "brotli": "^1.3.3", - "browndash-components": "^0.1.25", + "browndash-components": "^0.1.35", "browser-assert": "^1.2.1", "bson": "^4.6.1", "canvas": "^2.9.3", diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index d939470e9..8b2b77aca 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -512,7 +512,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} {toggle(this.maskButton, { display: !isInk ? 'none' : '' })} {toggle(this.hideImageButton, { display: !isImage ? 'none' : '' })} - {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })} + {toggle(this.chromeButton, { display: isNovice ? 'none' : '' })} {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })} {/* {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })} */} {toggle(this.snapButton, { display: !isCollection ? 'none' : '' })} diff --git a/src/client/views/PropertiesSection.tsx b/src/client/views/PropertiesSection.tsx index b72e048df..0e7cd7e92 100644 --- a/src/client/views/PropertiesSection.tsx +++ b/src/client/views/PropertiesSection.tsx @@ -8,7 +8,7 @@ import { StrCast } from '../../fields/Types'; export interface PropertiesSectionProps { title: string; - content?: JSX.Element | string | null; + children?: JSX.Element | string | null; isOpen: boolean; setIsOpen: (bool: boolean) => any; inSection?: boolean; @@ -33,7 +33,7 @@ export class PropertiesSection extends React.Component { @observable isDouble: boolean = false; render() { - if (this.props.content === undefined || this.props.content === null) return null; + if (this.props.children === undefined || this.props.children === null) return null; else return (
this.props.setInSection && this.props.setInSection(true))} onPointerLeave={action(() => this.props.setInSection && this.props.setInSection(false))}> @@ -58,7 +58,7 @@ export class PropertiesSection extends React.Component {
- {!this.props.isOpen ? null :
{this.props.content}
} + {!this.props.isOpen ? null :
{this.props.children}
}
); } diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss index 510defce4..2da2ec568 100644 --- a/src/client/views/PropertiesView.scss +++ b/src/client/views/PropertiesView.scss @@ -46,8 +46,7 @@ } .propertiesView-info { - margin-top: 20; - margin-right: 10; + margin-top: -5; float: right; font-size: 20; path { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 9cc75b1c6..b25ac7903 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Checkbox, Tooltip } from '@material-ui/core'; import { Colors, EditableText, IconButton, NumberInput, Size, Slider, Type } from 'browndash-components'; import { concat } from 'lodash'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import * as Icons from 'react-icons/bs'; //{BsCollectionFill, BsFillFileEarmarkImageFill} from "react-icons/bs" @@ -783,39 +783,39 @@ export class PropertiesView extends React.Component { } @computed get shapeXps() { - return this.getField('x'); + return NumCast(this.selectedDoc?.x); } @computed get shapeYps() { - return this.getField('y'); + return NumCast(this.selectedDoc?.y); } @computed get shapeHgt() { - return this.getField('_height'); + return NumCast(this.selectedDoc?._height); } @computed get shapeWid() { - return this.getField('_width'); + return NumCast(this.selectedDoc?._width); } set shapeXps(value) { - this.selectedDoc && (this.selectedDoc.x = Number(value)); + this.selectedDoc && (this.selectedDoc.x = Math.round(value * 100) / 100); } set shapeYps(value) { - this.selectedDoc && (this.selectedDoc.y = Number(value)); + this.selectedDoc && (this.selectedDoc.y = Math.round(value * 100) / 100); } set shapeWid(value) { - this.selectedDoc && (this.selectedDoc._width = Number(value)); + this.selectedDoc && (this.selectedDoc._width = Math.round(value * 100) / 100); } set shapeHgt(value) { - this.selectedDoc && (this.selectedDoc._height = Number(value)); + this.selectedDoc && (this.selectedDoc._height = Math.round(value * 100) / 100); } @computed get hgtInput() { return this.inputBoxDuo( 'hgt', this.shapeHgt, - undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height'), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = +val), 'set height'), 'H:', 'wid', this.shapeWid, - undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width'), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = +val), 'set width'), 'W:' ); } @@ -823,11 +823,11 @@ export class PropertiesView extends React.Component { return this.inputBoxDuo( 'Xps', this.shapeXps, - undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord'), + undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = +val), 'set x coord'), 'X:', 'Yps', this.shapeYps, - undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord'), + undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = +val), 'set y coord'), 'Y:' ); } @@ -1067,11 +1067,31 @@ export class PropertiesView extends React.Component { ); } - getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any) => { + _sliderBatch: UndoManager.Batch | undefined; + setFinalNumber = () => { + this._sliderBatch?.end(); + }; + getNumber = (label: string, unit: string, min: number, max: number, number: number, setNumber: any, autorange?: number, autorangeMinVal?: number) => { return ( -
+
- + (this._sliderBatch = UndoManager.StartBatch('slider ' + label))} + multithumb={false} + color={this.color} + size={Size.XSMALL} + min={min} + max={max} + autorangeMinVal={autorangeMinVal} + autorange={autorange} + number={number} + unit={unit} + decimals={1} + setFinalNumber={this.setFinalNumber} + setNumber={setNumber} + fillWidth + />
); }; @@ -1080,80 +1100,40 @@ export class PropertiesView extends React.Component { return (
{this.isInk ? this.controlPointsButton : null} - {this.getNumber( - 'Height', - ' px', - 0, - 1000, - Number(this.shapeHgt), - undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height') - )} - {this.getNumber( - 'Width', - ' px', - 0, - 1000, - Number(this.shapeWid), - undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width') - )} - {this.getNumber( - 'X', //'X Coordinate', - ' px', - -2000, - 2000, - Number(this.shapeXps), - undoable((val: string) => !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord') - )} - {this.getNumber( - 'Y', //'Y Coordinate', - ' px', - -2000, - 2000, - Number(this.shapeYps), - undoable((val: string) => !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord') - )} + {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)} + {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)} + {this.getNumber('X', ' px', this.shapeXps - 500, this.shapeXps + 500, this.shapeXps, (val: number) => !isNaN(val) && (this.shapeXps = val), 1000)} + {this.getNumber('Y', ' px', this.shapeYps - 500, this.shapeYps + 500, this.shapeYps, (val: number) => !isNaN(val) && (this.shapeYps = val), 1000)}
); } @computed get optionsSubMenu() { return ( - } - inSection={this.inOptions} - isOpen={this.openOptions} - setInSection={bool => (this.inOptions = bool)} - setIsOpen={bool => (this.openOptions = bool)} - onDoubleClick={this.CloseAll} - /> + (this.inOptions = bool)} setIsOpen={bool => (this.openOptions = bool)} onDoubleClick={this.CloseAll}> + + ); } @computed get sharingSubMenu() { return ( - - {/*
*/} -
- Layout Permissions - (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> -
- {/*
{"Re-distribute sharing settings"}
}> - + (this.openSharing = bool)} onDoubleClick={() => this.CloseAll()}> + <> + {/*
*/} +
+ Layout Permissions + (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> +
+ {/*
{"Re-distribute sharing settings"}
}> +
*/} - {/*
*/} - {this.sharingTable} - - } - isOpen={this.openSharing} - setIsOpen={bool => (this.openSharing = bool)} - onDoubleClick={() => this.CloseAll()} - /> + {/*
*/} + {this.sharingTable} + +
); } @@ -1183,17 +1163,11 @@ export class PropertiesView extends React.Component { @computed get filtersSubMenu() { return ( - - -
- } - isOpen={this.openFilters} - setIsOpen={bool => (this.openFilters = bool)} - onDoubleClick={() => this.CloseAll()} - /> + (this.openFilters = bool)} onDoubleClick={() => this.CloseAll()}> +
+ +
+
); } @@ -1202,36 +1176,46 @@ export class PropertiesView extends React.Component { return ( <> - (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()} /> - (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()} /> + (this.openAppearance = bool)} onDoubleClick={() => this.CloseAll()}> + {this.isInk ? this.appearanceEditor : null} + + (this.openTransform = bool)} onDoubleClick={() => this.CloseAll()}> + {this.transformEditor} + ); } @computed get fieldsSubMenu() { return ( - {Doc.noviceMode ? this.noviceFields : this.expandedField}
} - isOpen={this.openFields} - setIsOpen={bool => (this.openFields = bool)} - onDoubleClick={() => this.CloseAll()} - /> + (this.openFields = bool)} onDoubleClick={() => this.CloseAll()}> +
{Doc.noviceMode ? this.noviceFields : this.expandedField}
+
); } @computed get contextsSubMenu() { return ( - 0 ? this.contexts : 'There are no other contexts.'} isOpen={this.openContexts} setIsOpen={bool => (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()} /> + (this.openContexts = bool)} onDoubleClick={() => this.CloseAll()}> + {this.contextCount > 0 ? this.contexts : 'There are no other contexts.'} + ); } @computed get linksSubMenu() { - return 0 ? this.links : 'There are no current links.'} isOpen={this.openLinks} setIsOpen={bool => (this.openLinks = bool)} onDoubleClick={this.CloseAll} />; + return ( + (this.openLinks = bool)} onDoubleClick={this.CloseAll}> + {this.linkCount > 0 ? this.links : 'There are no current links.'} + + ); } @computed get layoutSubMenu() { - return (this.openLayout = bool)} onDoubleClick={this.CloseAll} />; + return ( + (this.openLayout = bool)} onDoubleClick={this.CloseAll}> + {this.layoutPreview} + + ); } @computed get description() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index ae9977d7a..da93a1b13 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -764,10 +764,10 @@ export class DocumentViewInternal extends DocComponent this.toggleFollowLink(false, false), icon: 'link' }); - onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); + !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (LinkManager.Links(this.Document).length) { - onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' }); + onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' }); onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' }); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 6fdde4b6b..42cc14f9c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -666,7 +666,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI), icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); - uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); + !Doc.noviceMode && uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && uicontrols.push({ description: 'Broadcast Message', @@ -2117,7 +2117,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
{this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection} - {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR ? null : this.sidebarHandle} + {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle} {this.audioHandle} - {this.layoutDoc._layout_enableAltContentUI ? this.overlayAlternateIcon : null} + {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null}
); diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 7e17008bb..6f07588b3 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -348,7 +348,7 @@ export const marks: { [index: string]: MarkSpec } = { excludes: 'user_mark', group: 'inline', toDOM(node: any) { - const uid = node.attrs.userid.replace('.', '').replace('@', ''); + const uid = node.attrs.userid.replace(/\./g, '').replace(/@/g, ''); const min = Math.round(node.attrs.modified / 60); const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index bb974ee74..df74cc9fe 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -25,4 +25,4 @@ export const Initializing = Symbol('DocInitializing'); export const ForceServerWrite = Symbol('DocForceServerWrite'); export const CachedUpdates = Symbol('DocCachedUpdates'); -export const DashVersion = 'v0.5.99'; +export const DashVersion = 'v0.6.00'; -- cgit v1.2.3-70-g09d2 From cfef242e9b76ba9caca2fc1871af74af6775538f Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Sep 2023 20:43:20 -0400 Subject: dropping link button on same collection makes a pushpin. fixed broken undo typing to crate doc in sidebar. fixed min/max scaling for cropped images and made annotationOverlays start to use it. Fixed nested text boxes to stopPropagation on pointer down to enable editing of translations in sidebar. moved sidebar filters onto doc's filters. Added metadata filters back to sidebar. Added an -any- option to filtersPanel. fixed schema view preview window, added buttons and sliders. --- package-lock.json | 119 ++++++++++++++++----- package.json | 4 +- src/client/documents/Documents.ts | 7 +- src/client/util/CurrentUserUtils.ts | 10 +- src/client/views/FilterPanel.tsx | 20 ++-- src/client/views/PropertiesView.tsx | 7 +- src/client/views/SidebarAnnos.tsx | 30 +++--- src/client/views/StyleProvider.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 +- .../CollectionStackingViewFieldColumn.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 44 +++++--- .../collectionSchema/CollectionSchemaView.scss | 8 +- .../collectionSchema/CollectionSchemaView.tsx | 1 + .../collections/collectionSchema/SchemaRowBox.tsx | 4 +- .../collectionSchema/SchemaTableCell.tsx | 26 ++++- src/client/views/global/globalScripts.ts | 8 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 - src/client/views/nodes/ImageBox.tsx | 10 +- src/client/views/nodes/PDFBox.tsx | 4 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 51 ++++++--- src/fields/Doc.ts | 1 + 21 files changed, 255 insertions(+), 110 deletions(-) (limited to 'src/fields') diff --git a/package-lock.json b/package-lock.json index e0e54769a..fbfdf0136 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7244,12 +7244,97 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "strip-ansi": { "version": "7.1.0", "bundled": true, "requires": { "ansi-regex": "^6.0.1" } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } } } }, @@ -8754,7 +8839,7 @@ } }, "string-width-cjs": { - "version": "npm:string-width@4.2.3", + "version": "npm:string-width-cjs@4.2.3", "bundled": true, "requires": { "emoji-regex": "^8.0.0", @@ -8777,7 +8862,7 @@ } }, "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", + "version": "npm:strip-ansi-cjs@6.0.1", "bundled": true, "requires": { "ansi-regex": "^5.0.1" @@ -8936,7 +9021,7 @@ } }, "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", + "version": "npm:wrap-ansi-cjs@7.0.0", "bundled": true, "requires": { "ansi-styles": "^4.0.0", @@ -14742,6 +14827,11 @@ "node-forge": "^0.10.0" } }, + "google-translate-api-browser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/google-translate-api-browser/-/google-translate-api-browser-3.0.1.tgz", + "integrity": "sha512-KTLodkyGBWMK9IW6QIeJ2zCuju4Z0CLpbkADKo+yLhbSTD4l+CXXpQ/xaynGVAzeBezzJG6qn8MLeqOq3SmW0A==" + }, "googleapis": { "version": "40.0.1", "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-40.0.1.tgz", @@ -27327,29 +27417,6 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "translate-google-api": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/translate-google-api/-/translate-google-api-1.0.4.tgz", - "integrity": "sha512-KVXmo4+64/H1vIbnzf2zNiJ2JLeEB3jrEnNRP2EFNAGNqna/5bmw/Cps3pCHu0n3BzTOoWh9u6wFvrRYdzQ6Iw==", - "requires": { - "axios": "^0.20.0" - }, - "dependencies": { - "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - } - } - }, "traverse-chain": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", diff --git a/package.json b/package.json index 67fdedeb2..d013ad74d 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,7 @@ "@types/three": "^0.126.2", "@types/web": "0.0.53", "@webscopeio/react-textarea-autocomplete": "^4.9.1", + "D": "^1.0.0", "adm-zip": "^0.4.16", "archiver": "^3.1.1", "array-batcher": "^1.2.3", @@ -196,7 +197,6 @@ "cors": "^2.8.5", "csv-parser": "^3.0.0", "csv-stringify": "^6.3.0", - "D": "^1.0.0", "d3": "^7.6.1", "depcheck": "^0.9.2", "equation-editor-react": "github:bobzel/equation-editor-react#useLocally", @@ -219,6 +219,7 @@ "golden-layout": "^1.5.9", "google-auth-library": "^4.2.4", "google-maps-react": "^2.0.6", + "google-translate-api-browser": "^3.0.1", "googleapis": "^40.0.0", "googlephotos": "^0.2.5", "got": "^12.0.1", @@ -327,7 +328,6 @@ "textarea-caret": "^3.1.0", "three": "^0.127.0", "tough-cookie": "^4.0.0", - "translate-google-api": "^1.0.4", "typescript-collections": "^1.3.3", "typescript-language-server": "^0.4.0", "url-loader": "^1.1.2", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bb60cb329..85ee4ef59 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -145,7 +145,6 @@ class CTypeInfo extends FInfo { class DTypeInfo extends FInfo { fieldType? = 'enumeration'; values? = Array.from(Object.keys(DocumentType)); - readOnly = true; } class DateInfo extends FInfo { fieldType? = 'date'; @@ -338,11 +337,13 @@ export class DocumentOptions { // freeform properties _freeform_backgroundGrid?: BOOLt = new BoolInfo('whether background grid is shown on freeform collections'); + _freeform_scale_min?: NUMt = new NumInfo('how far out a view can zoom (used by image/videoBoxes that are clipped'); + _freeform_scale_max?: NUMt = new NumInfo('how far in a view can zoom (used by sidebar freeform views'); _freeform_scale?: NUMt = new NumInfo('how much a freeform view has been scaled (zoomed)'); _freeform_panX?: NUMt = new NumInfo('horizontal pan location of a freeform view'); _freeform_panY?: NUMt = new NumInfo('vertical pan location of a freeform view'); _freeform_noAutoPan?: BOOLt = new BoolInfo('disables autopanning when this item is dragged'); - _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming'); + _freeform_noZoom?: BOOLt = new BoolInfo('disables zooming (used by Pile docs)'); //BUTTONS buttonText?: string; @@ -426,7 +427,7 @@ export class DocumentOptions { treeView_FreezeChildren?: STRt = new StrInfo('set (add, remove, add|remove) to disable adding, removing or both from collection'); sidebar_color?: string; // background color of text sidebar - sidebar_collectionType?: string; // collection type of text sidebar + sidebar_type_collection?: string; // collection type of text sidebar data_dashboards?: List; // list of dashboards used in shareddocs; text?: string; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8bedea562..527f251f9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -627,6 +627,11 @@ export class CurrentUserUtils { { title: "Z order", icon: "z", toolTip: "Keep Z order on Drag", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: '{ return toggleRaiseOnDrag(_readOnly_);}'}}, // Only when floating document is selected in freeform ] } + static stackTools(): Button[] { + return [ + { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + ] + } static viewTools(): Button[] { return [ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform @@ -682,8 +687,8 @@ export class CurrentUserUtils { static schemaTools():Button[] { return [ - {title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} }, - {title: "Single Lines", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, buttonText: "Single Line", icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} }, + {title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} }, + {title: "1 Line",toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} }, ]; } @@ -713,6 +718,7 @@ export class CurrentUserUtils { { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected ]; diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 0d4f4df5a..a601706ce 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -235,7 +235,7 @@ export class FilterPanel extends React.Component { facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); - const set = new Set([String.fromCharCode(127) + '--undefined--']); + const set = new Set([String.fromCharCode(127) + '--undefined--', Doc.FilterAny]); if (facetHeader === 'tags') allCollectionDocs.forEach(child => StrListCast(child[facetHeader]) @@ -257,24 +257,16 @@ export class FilterPanel extends React.Component { }; render() { - // console.log('this is frist one today ' + this._allFacets); - this._allFacets.forEach(element => console.log(element)); - // const options = Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => pair[1].filterable ).map(facet => value: facet, label: facet) //this._allFacets.filter(facet => this.activeFacetHeaders.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet })); - // console.log('HEELLLLLL ' + DocumentOptions); - - let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest']; + let filteredOptions: string[] = ['author', 'tags', 'text', 'acl-Guest', ...this._allFacets.filter(facet => facet[0] === facet.charAt(0).toUpperCase())]; Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => { if (pair[1].filterable) { filteredOptions.push(pair[0]); - console.log('THIS IS FILTERABLE ALKDJFIIEII' + filteredOptions); } }); let options = filteredOptions.map(facet => ({ value: facet, label: facet })); - // Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => console.log('this is first piar ' + pair[0] + ' this is second piar ' + pair[1].filterable)); - return (
@@ -398,13 +390,13 @@ export class FilterPanel extends React.Component {
filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) - ?.split(Doc.FilterSep)[2] === 'check' - } + ?.split(Doc.FilterSep)[2] ?? '' + )} type={type} - onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} + onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? (fval === Doc.FilterAny ? 'exists' : 'check') : 'remove'), 'set filter')} /> {facetValue}
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 699aafe80..21b6bfac5 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -18,7 +18,7 @@ import { ComputedField } from '../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl, normalizeEmail, SharingPermissions } from '../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; -import { DocumentType } from '../documents/DocumentTypes'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; import { GroupManager } from '../util/GroupManager'; import { LinkManager } from '../util/LinkManager'; @@ -122,6 +122,9 @@ export class PropertiesView extends React.Component { @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } + @computed get isStack() { + return this.selectedDoc?.type_collection === CollectionViewType.Stacking; + } rtfWidth = () => (!this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[Width](), this.props.width - 20)); rtfHeight = () => (!this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[Width]() ? Math.min(this.selectedDoc?.[Height](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT); @@ -1096,6 +1099,8 @@ export class PropertiesView extends React.Component { @computed get transformEditor() { return (
+ {!this.isStack ? null : this.getNumber('Gap', ' px', 0, 200, NumCast(this.selectedDoc!.gridGap), (val: number) => !isNaN(val) && (this.selectedDoc!.gridGap = val))} + {!this.isStack ? null : this.getNumber('xMargin', ' px', 0, 500, NumCast(this.selectedDoc!.xMargin), (val: number) => !isNaN(val) && (this.selectedDoc!.xMargin = val))} {this.isInk ? this.controlPointsButton : null} {this.getNumber('Width', ' px', 0, Math.max(1000, this.shapeWid), this.shapeWid, (val: number) => !isNaN(val) && (this.shapeWid = val), 1000, 1)} {this.getNumber('Height', ' px', 0, Math.max(1000, this.shapeHgt), this.shapeHgt, (val: number) => !isNaN(val) && (this.shapeHgt = val), 1000, 1)} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index ff347d65f..1e1b8e0e6 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -62,12 +62,9 @@ export class SidebarAnnos extends React.Component { DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author))); return Array.from(keys.keys()).sort(); } - get filtersKey() { - return '_' + this.sidebarKey + '_childFilters'; - } anchorMenuClick = (anchor: Doc, filterExlusions?: string[]) => { - const startup = StrListCast(this.props.rootDoc.childFilters) + const startup = this.childFilters() .map(filter => filter.split(':')[0]) .join(' '); const target = Docs.Create.TextDocument(startup, { @@ -151,8 +148,9 @@ export class SidebarAnnos extends React.Component { }; makeDocUnfiltered = (doc: Doc) => { if (DocListCast(this.props.rootDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { - if (this.props.layoutDoc[this.filtersKey]) { - this.props.layoutDoc[this.filtersKey] = new List(); + if (this.childFilters()) { + // if any child filters exist, get rid of them + this.props.layoutDoc._childFilters = new List(); } return true; } @@ -178,7 +176,7 @@ export class SidebarAnnos extends React.Component { addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey); moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey); removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey); - childFilters = () => [...StrListCast(this.props.layoutDoc._childFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])]; + childFilters = () => StrListCast(this.props.layoutDoc._childFilters); layout_showTitle = () => 'title'; setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight()); sortByLinkAnchorY = (a: Doc, b: Doc) => { @@ -188,25 +186,25 @@ export class SidebarAnnos extends React.Component { }; render() { const renderTag = (tag: string) => { - const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags::${tag}::check`); + const active = this.childFilters().includes(`tags${Doc.FilterSep}${tag}${Doc.FilterSep}check`); return ( -
Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, this.sidebarKey, e.shiftKey)}> +
Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, undefined, e.shiftKey)}> {tag}
); }; const renderMeta = (tag: string, dflt: FieldResult) => { - const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${dflt}:exists`); + const active = this.childFilters().includes(`${tag}${Doc.FilterSep}${Doc.FilterAny}${Doc.FilterSep}exists`); return ( -
Doc.setDocFilter(this.props.rootDoc, tag, dflt, 'exists', true, this.sidebarKey, e.shiftKey)}> +
Doc.setDocFilter(this.props.rootDoc, tag, Doc.FilterAny, 'exists', true, undefined, e.shiftKey)}> {tag}
); }; const renderUsers = (user: string) => { - const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`author:${user}:check`); + const active = this.childFilters().includes(`author:${user}:check`); return ( -
Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, this.sidebarKey, e.shiftKey)}> +
Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, undefined, e.shiftKey)}> {user}
); @@ -224,11 +222,11 @@ export class SidebarAnnos extends React.Component { height: '100%', }}>
e.stopPropagation()}> - {this.allUsers.map(renderUsers)} + {this.allUsers.length > 1 ? this.allUsers.map(renderUsers) : null} {this.allHashtags.map(renderTag)} - {/* {Array.from(this.allMetadata.keys()) + {Array.from(this.allMetadata.keys()) .sort() - .map(key => renderMeta(key, this.allMetadata.get(key)))} */} + .map(key => renderMeta(key, this.allMetadata.get(key)))}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 85aa5ad83..0a14b93f7 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -317,7 +317,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ({ + items={[ DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard)!, ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])].map(dv => ({ text: StrCast(dv.rootDoc.title), val: dv as any, style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor}, diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 9ba4cb6cf..8c40567d3 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -138,7 +138,7 @@ export class CollectionStackingView extends CollectionSubView diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 3aadeffcd..3598d548a 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -361,7 +361,11 @@ export class CollectionStackingViewFieldColumn extends React.Component +
e.stopPropagation()} + className="collectionStackingView-addDocumentButton" + style={{ width: 'calc(100% - 25px)', maxWidth: this.props.columnWidth / this.props.numGroupColumns - 25, marginBottom: 10 }}> number; @@ -421,11 +422,27 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.Document._isGroup || this.Document._freeform_noZoom) return; + if (this.Document._isGroup || this.Document[this.scaleFieldKey + '_noZoom']) return; let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.getTransform().transformPoint(pointX, pointY); @@ -1032,12 +1049,15 @@ export class CollectionFreeFormView extends CollectionSubView 20) { deltaScale = 20 / invTransform.Scale; } - if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) { + if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc[this.scaleFieldKey + '_min'])) { this.setPan(0, 0); return; } - if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) { - deltaScale = NumCast(this.rootDoc._freeform_scale_min, 1) / invTransform.Scale; + if (deltaScale * invTransform.Scale > NumCast(this.rootDoc[this.scaleFieldKey + '_max'], Number.MAX_VALUE)) { + deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_max'], 1) / invTransform.Scale; + } + if (deltaScale * invTransform.Scale < NumCast(this.rootDoc[this.scaleFieldKey + '_min'], this.isAnnotationOverlay ? 1 : 0)) { + deltaScale = NumCast(this.rootDoc[this.scaleFieldKey + '_min'], 1) / invTransform.Scale; } const localTransform = invTransform.scaleAbout(deltaScale, x, y); @@ -1527,12 +1547,6 @@ export class CollectionFreeFormView extends CollectionSubView .schema-column-header:nth-child(2) > .left { @@ -205,7 +211,7 @@ overflow-x: hidden; overflow-y: auto; padding: 5px; - display: inline-block; + display: inline-flex; } .schema-row { diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 5c7dcc1a4..d757d5349 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -836,6 +836,7 @@ export class CollectionSchemaView extends CollectionSubView() {
this.props.isContentActive() && e.stopPropagation()} ref={r => { // prevent wheel events from passively propagating up through containers diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 5b4fc34bb..4e418728f 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -16,7 +16,7 @@ import { CollectionSchemaView } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaTableCell } from './SchemaTableCell'; import { Transform } from '../../../util/Transform'; -import { IconButton } from 'browndash-components'; +import { IconButton, Size } from 'browndash-components'; import { CgClose } from 'react-icons/cg'; import { FaExternalLinkAlt } from 'react-icons/fa'; import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../../Utils'; @@ -117,6 +117,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { } + size={Size.XSMALL} onPointerDown={e => setupMoveUpEvents( this, @@ -133,6 +134,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { } + size={Size.XSMALL} onPointerDown={e => setupMoveUpEvents( this, diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 1c9c0de53..e18f27fb0 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -325,10 +325,34 @@ export class SchemaEnumerationCell extends React.Component const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this.props); const options = this.props.options?.map(facet => ({ value: facet, label: facet })); return ( -
+
(this.showLayoutAcl = !this.showLayoutAcl))} /> @@ -843,11 +844,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
) : null}   */} +
-
- ) : ( -
- ); + ) : ( +
+ ); const titleArea = this._editingTitle ? ( Date: Mon, 18 Sep 2023 14:27:05 -0400 Subject: revised how to do filtering by links. --- src/client/documents/Documents.ts | 28 +++++++--------------------- src/client/views/FilterPanel.tsx | 15 +++++++-------- src/client/views/InkTangentHandles.tsx | 1 - src/client/views/nodes/MapBox/MapBox.tsx | 6 +++--- src/fields/Doc.ts | 25 ++++++++++++++++++++----- 5 files changed, 37 insertions(+), 38 deletions(-) (limited to 'src/fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 055950e6f..254be67b9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; import { inheritParentAcls, SharingPermissions } from '../../fields/util'; import { Upload } from '../../server/SharedMediaTypes'; -import { aggregateBounds, OmitKeys, Utils } from '../../Utils'; +import { OmitKeys, Utils } from '../../Utils'; import { YoutubeBox } from '../apis/youtube/YoutubeBox'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; @@ -1296,20 +1296,6 @@ export namespace DocUtils { for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies' && fkey !== Utils.noDragsDocFilter.split(Doc.FilterSep)[0])) { const facet = filterFacets[facetKey]; - let links = true; - const linkedTo = filterFacets['-linkedTo'] && Array.from(Object.keys(filterFacets['-linkedTo']))?.[0]; - const linkedToField = filterFacets['-linkedTo']?.[linkedTo]; - const allLinks = linkedTo && linkedToField ? LinkManager.Instance.getAllRelatedLinks(d) : []; - // prettier-ignore - if (linkedTo) { - if (allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_1)?.[linkedToField]))) || // - allLinks.some(d => linkedTo === Field.toScriptString(DocCast(DocCast(d.link_anchor_2)?.[linkedToField])))) - { - links = true; - } - else links = false - } - // facets that match some value in the field of the document (e.g. some text field) const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); @@ -1319,16 +1305,16 @@ export namespace DocUtils { // metadata facets that exist const exists = Object.keys(facet).filter(value => facet[value] === 'exists'); - // metadata facets that exist + // facets that unset metadata (a hack for making cookies work) const unsets = Object.keys(facet).filter(value => facet[value] === 'unset'); - // facets that have an x next to them + // facets that specify that a field must not match a specific value const xs = Object.keys(facet).filter(value => facet[value] === 'x'); - if (!linkedTo && !unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; + if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined); + const satisfiesExistsFacets = !exists.length ? true : exists.some(value => (facetKey !== '-linkedTo' ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length)); const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); const satisfiesMatchFacets = !matches.length ? true @@ -1344,11 +1330,11 @@ export namespace DocUtils { }); // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria if (parentCollection?.childFilters_boolean === 'OR') { - if (links && satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; + if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; } // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria else { - if (!links || !satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; + if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; } } return parentCollection?.childFilters_boolean === 'OR' ? false : true; diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 4af29c70e..312dc3a70 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -56,7 +56,7 @@ export class FilterPanel extends React.Component { @computed get _allFacets() { // trace(); - const noviceReqFields = ['author', 'tags', 'text', 'type']; + const noviceReqFields = ['author', 'tags', 'text', 'type', '-linkedTo']; const noviceLayoutFields: string[] = []; //["_layout_curPage"]; const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; @@ -118,7 +118,7 @@ export class FilterPanel extends React.Component { // } gatherFieldValues(childDocs: Doc[], facetKey: string) { - const valueSet = new Set(); + const valueSet = new Set(StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1])); let rtFields = 0; let subDocs = childDocs; if (subDocs.length > 0) { @@ -127,7 +127,6 @@ export class FilterPanel extends React.Component { newarray = []; subDocs.forEach(t => { const facetVal = t[facetKey]; - // console.log("facetVal " + facetVal) if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++; facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field)); (facetVal === true || facetVal == false) && valueSet.add(Field.toString(!facetVal)); @@ -182,14 +181,14 @@ export class FilterPanel extends React.Component { }); if (facetHeader === 'text') { - return { facetHeader: facetHeader, renderType: 'text' }; + return { facetHeader, renderType: 'text' }; } else if (facetHeader !== 'tags' && nonNumbers / facetValues.strings.length < 0.1) { const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1)); const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05))); const ranged = Doc.readDocRangeFilter(this.targetDoc, facetHeader); // not the filter range, but the zooomed in range on the filter - return { facetHeader: facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; + return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged ? ranged : [extendedMinVal, extendedMaxVal] }; } else { - return { facetHeader: facetHeader, renderType: 'checkbox' }; + return { facetHeader, renderType: 'checkbox' }; } }) ); @@ -236,7 +235,7 @@ export class FilterPanel extends React.Component { facetValues = (facetHeader: string) => { const allCollectionDocs = new Set(); SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); - const set = new Set([String.fromCharCode(127) + '--undefined--', Doc.FilterAny]); + const set = new Set([...StrListCast(this.props.rootDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); if (facetHeader === 'tags') allCollectionDocs.forEach(child => StrListCast(child[facetHeader]) @@ -404,7 +403,7 @@ export class FilterPanel extends React.Component { ?.split(Doc.FilterSep)[2] ?? '' )} type={type} - onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? (fval === Doc.FilterAny ? 'exists' : 'check') : 'remove'), 'set filter')} + onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue}
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index 71e0ff63c..65f6a6dfa 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -12,7 +12,6 @@ import { UndoManager } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; -import { DocumentView } from './nodes/DocumentView'; export interface InkHandlesProps { inkDoc: Doc; inkView: InkingStroke; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 3e22c0d0e..d7469e530 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -350,7 +350,7 @@ export class MapBox extends ViewBoxAnnotatableComponent color !== '' && DashColor(color).alpha() !== 1; return isTransparent(StrCast(doc[key])); } + if (key === '-linkedTo') { + // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) + const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); + const matchLink = (value: string, anchor: Doc) => { + const linkedToExp = value?.split('='); + if (linkedToExp.length === 1) return Field.toScriptString(anchor) === value; + return Field.toScriptString(DocCast(anchor[linkedToExp[0]])) === linkedToExp[1]; + }; + // prettier-ignore + return (value === Doc.FilterNone && !allLinks.length) || + (value === Doc.FilterAny && !!allLinks.length) || + (allLinks.some(link => matchLink(value,DocCast(link.link_anchor_1)) || + matchLink(value,DocCast(link.link_anchor_2)) )); + } if (typeof value === 'string') { value = value.replace(`,${Utils.noRecursionHack}`, ''); } const fieldVal = doc[key]; if (Cast(fieldVal, listSpec('string'), []).length) { - const vals = Cast(fieldVal, listSpec('string'), []); + const vals = StrListCast(fieldVal); const docs = vals.some(v => (v as any) instanceof Doc); if (docs) return value === Field.toString(fieldVal as Field); return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value) || (value === String.fromCharCode(127) + '--undefined--' && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return fieldStr.includes(value) || (value === Doc.FilterAny && fieldVal !== undefined) || (value === Doc.FilterNone && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { @@ -1520,18 +1534,19 @@ export namespace Doc { export const FilterSep = '::'; export const FilterAny = '--any--'; + export const FilterNone = '--undefined--'; // filters document in a container collection: // all documents with the specified value for the specified key are included/excluded // based on the modifiers :"check", "x", undefined - export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'removeAll' | 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { + export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { if (!container) return; const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'childFilters'; const childFilters = StrListCast(container[filterField]); runInAction(() => { for (let i = 0; i < childFilters.length; i++) { const fields = childFilters[i].split(FilterSep); // split key:value:modifier - if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || modifiers === 'removeAll' || (fields[2] === 'match' && modifiers === 'remove'))) { + if (fields[0] === key && (fields[1] === value.toString() || modifiers === 'match' || (fields[2] === 'match' && modifiers === 'remove'))) { if (fields[2] === modifiers && modifiers && fields[1] === value.toString()) { if (toggle) modifiers = 'remove'; else return; @@ -1543,7 +1558,7 @@ export namespace Doc { } if (!childFilters.length && modifiers === 'match' && value === undefined) { container[filterField] = undefined; - } else if (modifiers !== 'remove' && modifiers !== 'removeAll') { + } else if (modifiers !== 'remove') { !append && (childFilters.length = 0); childFilters.push(key + FilterSep + value + FilterSep + modifiers); container[filterField] = new List(childFilters); -- cgit v1.2.3-70-g09d2 From 84aa8806a62e2e957e8281d7d492139e3d8225f2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 18 Sep 2023 14:41:53 -0400 Subject: removed filter checkboxes for list values since they don't work. fixed any/undefined filtering for tags or other list fields. --- src/client/views/FilterPanel.tsx | 8 ++++++-- src/fields/Doc.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) (limited to 'src/fields') diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 312dc3a70..69ceb0f65 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -17,6 +17,7 @@ import { FieldView } from './nodes/FieldView'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { SettingsManager } from '../util/SettingsManager'; import { Id } from '../../fields/FieldSymbols'; +import { List } from '../../fields/List'; interface filterProps { rootDoc: Doc; @@ -245,8 +246,11 @@ export class FilterPanel extends React.Component { else allCollectionDocs.forEach(child => { const fieldVal = child[facetHeader] as Field; - set.add(Field.toString(fieldVal)); - (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString()); + if (!(fieldVal instanceof List)) { + // currently we have no good way of filtering based on a field that is a list + set.add(Field.toString(fieldVal)); + (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString()); + } }); const facetValues = Array.from(set).filter(v => v); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 78a03d782..56b97e42f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1481,14 +1481,18 @@ export namespace Doc { value = value.replace(`,${Utils.noRecursionHack}`, ''); } const fieldVal = doc[key]; + // prettier-ignore + if ((value === Doc.FilterAny && fieldVal !== undefined) || + (value === Doc.FilterNone && fieldVal === undefined)) { + return true; + } if (Cast(fieldVal, listSpec('string'), []).length) { const vals = StrListCast(fieldVal); const docs = vals.some(v => (v as any) instanceof Doc); if (docs) return value === Field.toString(fieldVal as Field); return vals.some(v => v.includes(value)); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } - const fieldStr = Field.toString(fieldVal as Field); - return fieldStr.includes(value) || (value === Doc.FilterAny && fieldVal !== undefined) || (value === Doc.FilterNone && fieldVal === undefined); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring + return Field.toString(fieldVal as Field).includes(value); // bcz: arghh: Todo: comparison should be parameterized as exact, or substring } export function deiconifyView(doc: Doc) { -- cgit v1.2.3-70-g09d2 From 34c64c47444e89bf900227a4f3c56d9023f5d4f3 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 21 Sep 2023 14:46:26 -0400 Subject: fixed zip export to include media properly. removed ClientUtils which wasn't being used. --- src/fields/Doc.ts | 29 +++++++++++++++++++---------- src/server/server_Initialization.ts | 10 +++++----- 2 files changed, 24 insertions(+), 15 deletions(-) (limited to 'src/fields') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 56b97e42f..27da65342 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -853,16 +853,24 @@ export namespace Doc { export async function Zip(doc: Doc, zipFilename = 'dashExport.zip') { const { clone, map, linkMap } = await Doc.MakeClone(doc); const proms = new Set(); - function replacer(key: any, value: any) { + function replacer(key: any, value: any) { if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; - if (value instanceof ImageField) { - const extension = value.url.href.replace(/.*\./, ''); - proms.add(value.url.href.replace('.' + extension, '_o.' + extension)); - return SerializationHelper.Serialize(value); + if (value.__type === 'image') { + const extension = value.url.replace(/.*\./, ''); + proms.add(value.url.replace('.' + extension, '_o.' + extension)); + return SerializationHelper.Serialize(new ImageField(value.url)); } - if (value instanceof PdfField || value instanceof AudioField || value instanceof VideoField) { - proms.add(value.url.href); - return SerializationHelper.Serialize(value); + if (value.__type === 'pdf') { + proms.add(value.url); + return SerializationHelper.Serialize(new PdfField(value.url)); + } + if (value.__type === 'audio') { + proms.add(value.url); + return SerializationHelper.Serialize(new AudioField(value.url)); + } + if (value.__type === 'video') { + proms.add(value.url); + return SerializationHelper.Serialize(new VideoField(value.url)); } if ( value instanceof Doc || @@ -889,14 +897,15 @@ export namespace Doc { const zip = new JSZip(); var count = 0; - const promArr = Array.from(proms).filter(url => url.startsWith(window.location.origin)); + const promArr = Array.from(proms).filter(url => url?.startsWith("/files")).map(url => url.replace("/",""))// window.location.origin)); + console.log(promArr.length); if (!promArr.length) { zip.file('docs.json', jsonDocs); zip.generateAsync({ type: 'blob' }).then(content => saveAs(content, zipFilename)); } else promArr.forEach((url, i) => { // loading a file and add it in a zip file - JSZipUtils.getBinaryContent(url, (err: any, data: any) => { + JSZipUtils.getBinaryContent(window.location.origin+"/"+url, (err: any, data: any) => { if (err) throw err; // or handle the error // // Generate a directory within the Zip file structure // const assets = zip.folder("assets"); diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index 839091194..ccb709453 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -121,11 +121,11 @@ function determineEnvironment() { const label = isRelease ? 'release' : 'development'; console.log(`\nrunning server in ${color(label)} mode`); - // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE - // on the client side, thanks to dotenv in webpack.config.js - let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8'); - clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; - fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8'); + // // swilkins: I don't think we need to read from ClientUtils.RELEASE anymore. Should be able to invoke process.env.RELEASE + // // on the client side, thanks to dotenv in webpack.config.js + // let clientUtils = fs.readFileSync('./src/client/util/ClientUtils.ts.temp', 'utf8'); + // clientUtils = `//AUTO-GENERATED FILE: DO NOT EDIT\n${clientUtils.replace('"mode"', String(isRelease))}`; + // fs.writeFileSync('./src/client/util/ClientUtils.ts', clientUtils, 'utf8'); return isRelease; } -- cgit v1.2.3-70-g09d2 From df112b865e068fe589327e7424f4155c76881539 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 21 Sep 2023 15:01:48 -0400 Subject: fixed export of media --- src/fields/Doc.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/fields') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 27da65342..7170be6cc 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -855,20 +855,20 @@ export namespace Doc { const proms = new Set(); function replacer(key: any, value: any) { if (key && ['branchOf', 'cloneOf', 'cursors'].includes(key)) return undefined; - if (value.__type === 'image') { + if (value?.__type === 'image') { const extension = value.url.replace(/.*\./, ''); proms.add(value.url.replace('.' + extension, '_o.' + extension)); return SerializationHelper.Serialize(new ImageField(value.url)); } - if (value.__type === 'pdf') { + if (value?.__type === 'pdf') { proms.add(value.url); return SerializationHelper.Serialize(new PdfField(value.url)); } - if (value.__type === 'audio') { + if (value?.__type === 'audio') { proms.add(value.url); return SerializationHelper.Serialize(new AudioField(value.url)); } - if (value.__type === 'video') { + if (value?.__type === 'video') { proms.add(value.url); return SerializationHelper.Serialize(new VideoField(value.url)); } -- cgit v1.2.3-70-g09d2