aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--package-lock.json6
-rw-r--r--package.json4
-rw-r--r--src/client/ClientRecommender.tsx2
-rw-r--r--src/client/documents/Documents.ts32
-rw-r--r--src/client/util/CurrentUserUtils.ts26
-rw-r--r--src/client/util/GroupManager.tsx5
-rw-r--r--src/client/util/SelectionManager.ts2
-rw-r--r--src/client/util/SettingsManager.scss18
-rw-r--r--src/client/util/SharingManager.tsx118
-rw-r--r--src/client/views/EditableView.scss2
-rw-r--r--src/client/views/EditableView.tsx1
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/MainView.tsx10
-rw-r--r--src/client/views/PropertiesView.scss61
-rw-r--r--src/client/views/PropertiesView.tsx396
-rw-r--r--src/client/views/StyleProvider.tsx85
-rw-r--r--src/client/views/collections/CollectionSubView.tsx16
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx5
-rw-r--r--src/client/views/collections/CollectionView.tsx16
-rw-r--r--src/client/views/collections/TreeView.scss41
-rw-r--r--src/client/views/collections/TreeView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.scss57
-rw-r--r--src/client/views/nodes/DocumentView.tsx12
-rw-r--r--src/client/views/nodes/FilterBox.scss164
-rw-r--r--src/client/views/nodes/FilterBox.tsx309
-rw-r--r--src/fields/Doc.ts10
27 files changed, 1113 insertions, 294 deletions
diff --git a/.gitignore b/.gitignore
index 6d4b98289..43b63a362 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,6 @@ src/server/session_manager/logs/**/*.log
*.crt
*.key
*.pfx
-*.properties \ No newline at end of file
+*.properties
+debug.log
+.vscodeignore \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index c0206ef4c..fa8f7259b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9305,9 +9305,9 @@
"integrity": "sha512-vTgEjKjS89C5yHL5qWPpT6BzKuOVqABp+A3Szpbx34pIy3sngxlGaFpgHhfj6fKze1w0QKeOSDbU7SKu7wDvRQ=="
},
"mobx": {
- "version": "5.15.4",
- "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.4.tgz",
- "integrity": "sha512-xRFJxSU2Im3nrGCdjSuOTFmxVDGeqOHL+TyADCGbT0k4HHqGmx5u2yaHNryvoORpI4DfbzjJ5jPmuv+d7sioFw=="
+ "version": "5.15.7",
+ "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
+ "integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw=="
},
"mobx-react": {
"version": "5.4.4",
diff --git a/package.json b/package.json
index 9b782c057..7ea6fdc6f 100644
--- a/package.json
+++ b/package.json
@@ -186,8 +186,8 @@
"lodash": "^4.17.15",
"material-ui": "^0.20.2",
"mobile-detect": "^1.4.4",
- "mobx": "^5.15.3",
- "mobx-react": "^5.3.5",
+ "mobx": "^5.15.7",
+ "mobx-react": "^5.4.4",
"mobx-react-devtools": "^6.1.1",
"mobx-utils": "^5.6.1",
"mongodb": "^3.5.9",
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
index 3f875057e..1d4653471 100644
--- a/src/client/ClientRecommender.tsx
+++ b/src/client/ClientRecommender.tsx
@@ -40,8 +40,6 @@ const fieldkey = "data";
@observer
export class ClientRecommender extends React.Component<RecommenderProps> {
-
-
static Instance: ClientRecommender;
private mainDoc?: RecommenderDocument;
private docVectors: Set<RecommenderDocument> = new Set();
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9406b374e..9bf3e7fc0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -171,7 +171,7 @@ export class DocumentOptions {
description?: string; // added for links
layout?: string | Doc; // default layout string for a document
contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
- childLimitHeight?: number; // whether to limit the height of colleciton children. 0 - means height can be no bigger than width
+ childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width
childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view)
childLayoutString?: string; // template string for collection to use to render its children
childDontRegisterViews?: boolean;
@@ -949,7 +949,14 @@ export namespace DocUtils {
}
return false;
}
- export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField) {
+ /**
+ * @param docs
+ * @param docFilters
+ * @param docRangeFilters
+ * @param viewSpecScript
+ * Given a list of docs and docFilters, @returns the list of Docs that match those filters
+ */
+ export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField, parentCollection?: Doc) {
const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
if (!docFilters?.length && !docRangeFilters?.length) {
return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one
@@ -973,11 +980,20 @@ 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")) {
const facet = filterFacets[facetKey];
+
+ // 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");
+
+ // facets that have a check next to them
const checks = Object.keys(facet).filter(value => facet[value] === "check");
+
+ // facets that have an x next to them
const xs = Object.keys(facet).filter(value => facet[value] === "x");
+
+ if (!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 satisfiesMatchFacets = !matches.length ? true : matches.some(value => {
@@ -989,11 +1005,17 @@ export namespace DocUtils {
}
return Field.toString(d[facetKey] as Field).includes(value);
});
- if (!satisfiesCheckFacets || !satisfiesMatchFacets || failsNotEqualFacets) {
- return false;
+ // 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 (((FilterBox._filterScope === "Current Collection" ? parentCollection || CurrentUserUtils.ActiveDashboard : CurrentUserUtils.ActiveDashboard).currentFilter as Doc)?.filterBoolean === "OR") {
+ if (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 (!satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false;
}
+
}
- return true;
+ return ((FilterBox._filterScope === "Current Collection" ? parentCollection || CurrentUserUtils.ActiveDashboard : CurrentUserUtils.ActiveDashboard).currentFilter as Doc)?.filterBoolean === "OR" ? false : true;
}) : childDocs;
const rangeFilteredDocs = filteredDocs.filter(d => {
for (let i = 0; i < docRangeFilters.length; i += 3) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 0fb32970a..ea27b7327 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -515,7 +515,7 @@ export class CurrentUserUtils {
{ title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' },
{ title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc },
{ title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' },
- { title: "Filter", target: Cast(doc.myFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' },
+ // { title: "Filter", target: Cast(doc.currentFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' },
{ title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' },
{ title: "My Files", target: Cast(doc.myFilesystem, Doc, null), icon: "file", click: 'selectMainMenu(self)' },
{ title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' },
@@ -803,20 +803,20 @@ export class CurrentUserUtils {
}
}
static setupFilterDocs(doc: Doc) {
- // setup Recently Closed library item
- doc.myFilter === undefined;
- if (doc.myFilter === undefined) {
- doc.myFilter = new PrefetchProxy(Docs.Create.FilterDocument({
- title: "FilterDoc",
+ // setup Filter item
+ doc.currentFilter === undefined;
+ if (doc.currentFilter === undefined) {
+ doc.currentFilter = Docs.Create.FilterDocument({
+ title: "FilterDoc", _height: 150,
treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none",
treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true,
_lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true
- }));
+ });
+ const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`);
+ (doc.currentFilter as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
+ (doc.currentFilter as Doc).contextMenuLabels = new List<string>(["Clear All"]);
+ (doc.currentFilter as Doc).filterBoolean = "AND";
}
- const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([]); scriptContext._docFilters = scriptContext._docRangeFilters = undefined;`, { scriptContext: Doc.name });
- (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]);
- (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]);
-
}
static setupUserDoc(doc: Doc) {
@@ -848,7 +848,7 @@ export class CurrentUserUtils {
CurrentUserUtils.setupPresentations(doc);
CurrentUserUtils.setupFilesystem(doc);
CurrentUserUtils.setupRecentlyClosedDocs(doc);
- CurrentUserUtils.setupFilterDocs(doc);
+ // CurrentUserUtils.setupFilterDocs(doc);
CurrentUserUtils.setupUserDoc(doc);
}
@@ -1013,6 +1013,8 @@ export class CurrentUserUtils {
doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); //
doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); //
Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]);
+ doc.savedFilters = new List<Doc>();
+ doc.filterDocCount = 0;
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
this.setupDocTemplates(doc); // sets up the template menu of templates
this.setupImportSidebar(doc);
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index 68af67a3b..62d656c5d 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,5 +1,5 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import * as React from "react";
import Select from 'react-select';
@@ -284,8 +284,7 @@ export class GroupManager extends React.Component<{}> {
placeholder="Group name"
onChange={action(() => this.buttonColour = this.inputRef.current?.value ? "black" : "#979797")} />
<Select
- isMulti={true}
- isSearchable={true}
+ isMulti
options={this.options}
onChange={this.handleChange}
placeholder={"Select users"}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index f657e5b40..b132af035 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -66,7 +66,7 @@ export namespace SelectionManager {
manager.SelectSchemaView(colSchema, document);
}
- const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wraapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
+ const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed
return manager.SelectedViews.get(doc) ? true : false;
});
// computed functions, such as used in IsSelected generate errors if they're called outside of a
diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss
index 5ca54517c..d8342ea56 100644
--- a/src/client/util/SettingsManager.scss
+++ b/src/client/util/SettingsManager.scss
@@ -75,8 +75,8 @@
width: 130;
color: black;
border-radius: 5px;
- padding:7px;
-
+ padding: 7px;
+
}
}
@@ -169,7 +169,7 @@
}
}
-.prefs-content{
+.prefs-content {
text-align: left;
}
@@ -210,6 +210,7 @@
.preferences-font-controls {
display: flex;
justify-content: space-between;
+ width: 130%;
}
.font-select {
@@ -429,11 +430,12 @@
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
- }
+ }
- .tab-column-title, .tab-column-content {
- padding-left: 16px;
- }
+ .tab-column-title,
+ .tab-column-content {
+ padding-left: 16px;
+ }
}
@@ -462,4 +464,4 @@
.settings-interface .settings-heading {
font-size: 25;
}
-}
+} \ No newline at end of file
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index 2aea73528..08dfb5066 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -7,7 +7,7 @@ import Select from "react-select";
import * as RequestPromise from "request-promise";
import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
-import { Cast, StrCast } from "../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../fields/Types";
import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from "../../fields/util";
import { Utils } from "../../Utils";
import { DocServer } from "../DocServer";
@@ -78,7 +78,16 @@ export class SharingManager extends React.Component<{}> {
@observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
private populating: boolean = false; // whether the list of users is populating or not
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
- @observable private myDocAcls: boolean = false;
+ @observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
+
+ // maps acl symbols to SharingPermissions
+ private AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit],
+ [AclAdmin, SharingPermissions.Admin]
+ ]);
// private get linkVisible() {
// return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
@@ -156,6 +165,33 @@ export class SharingManager extends React.Component<{}> {
}
/**
+ * Shares the document with a user.
+ */
+ setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {
+ const { user, sharingDoc } = recipient;
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl-${normalizeEmail(user.email)}`;
+ const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
+
+ const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
+ docs.forEach(doc => {
+ doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc);
+
+ if (permission === SharingPermissions.None) {
+ if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 1) - 1;
+ }
+ else {
+ if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numUsersShared = NumCast(doc.numUsersShared, 0) + 1;
+ }
+
+ distributeAcls(acl, permission as SharingPermissions, doc);
+
+ if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc);
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
+ });
+ }
+
+ /**
* Sets the permission on the target for the group.
* @param group
* @param permission
@@ -170,6 +206,14 @@ export class SharingManager extends React.Component<{}> {
docs.forEach(doc => {
doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc);
+
+ if (permission === SharingPermissions.None) {
+ if (doc[acl] && doc[acl] !== SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 1) - 1;
+ }
+ else {
+ if (!doc[acl] || doc[acl] === SharingPermissions.None) doc.numGroupsShared = NumCast(doc.numGroupsShared, 0) + 1;
+ }
+
distributeAcls(acl, permission as SharingPermissions, doc);
if (group instanceof Doc) {
@@ -265,21 +309,21 @@ export class SharingManager extends React.Component<{}> {
/**
* Shares the document with a user.
*/
- setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {
- const { user, sharingDoc } = recipient;
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(user.email)}`;
- const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
-
- const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
- docs.forEach(doc => {
- doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc);
- distributeAcls(acl, permission as SharingPermissions, doc);
-
- if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc);
- else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
- });
- }
+ // setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => {
+ // const { user, sharingDoc } = recipient;
+ // const target = targetDoc || this.targetDoc!;
+ // const acl = `acl-${normalizeEmail(user.email)}`;
+ // const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`;
+
+ // const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
+ // docs.forEach(doc => {
+ // doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc);
+ // distributeAcls(acl, permission as SharingPermissions, doc);
+
+ // if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc);
+ // else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc));
+ // });
+ // }
// private setExternalSharing = (permission: string) => {
@@ -313,11 +357,11 @@ export class SharingManager extends React.Component<{}> {
if (!uniform) dropdownValues.unshift("-multiple-");
if (override) dropdownValues.unshift("None");
return dropdownValues.filter(permission => permission !== SharingPermissions.View).map(permission =>
- (
- <option key={permission} value={permission}>
- {permission === SharingPermissions.Add ? "Can Augment" : permission}
- </option>
- )
+ (
+ <option key={permission} value={permission}>
+ {permission === SharingPermissions.Add ? "Can Augment" : permission}
+ </option>
+ )
);
}
@@ -397,20 +441,12 @@ export class SharingManager extends React.Component<{}> {
}
distributeOverCollection = (targetDoc?: Doc) => {
- const AclMap = new Map<symbol, string>([
- [AclPrivate, SharingPermissions.None],
- [AclReadonly, SharingPermissions.View],
- [AclAddonly, SharingPermissions.Add],
- [AclEdit, SharingPermissions.Edit],
- [AclAdmin, SharingPermissions.Admin]
- ]);
-
const target = targetDoc || this.targetDoc!;
const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document);
docs.forEach(doc => {
for (const [key, value] of Object.entries(doc[AclSym])) {
- distributeAcls(key, AclMap.get(value)! as SharingPermissions, target);
+ distributeAcls(key, this.AclMap.get(value)! as SharingPermissions, target);
}
});
}
@@ -471,7 +507,8 @@ export class SharingManager extends React.Component<{}> {
const targetDoc = docs[0];
// tslint:disable-next-line: no-unnecessary-callback-wrapper
- const admin = this.myDocAcls ? Boolean(docs.length) : docs.map(doc => GetEffectiveAcl(doc)).every(acl => acl === AclAdmin); // if the user has admin access to all selected docs
+ const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc));
+ const admin = this.myDocAcls ? Boolean(docs.length) : effectiveAcls.every(acl => acl === AclAdmin);
// users in common between all docs
const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym])));
@@ -535,7 +572,7 @@ export class SharingManager extends React.Component<{}> {
<span className={"padding"}>Me</span>
<div className="edit-actions">
<div className={"permissions-dropdown"}>
- {targetDoc?.[`acl-${Doc.CurrentUserEmailNormalized}`]}
+ {effectiveAcls.every(acl => acl === effectiveAcls[0]) ? this.AclMap.get(effectiveAcls[0])! : "-multiple-"}
</div>
</div>
</div>
@@ -545,7 +582,7 @@ export class SharingManager extends React.Component<{}> {
// the list of groups shared with
const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true);
- groupListMap.unshift({ title: "Public" }, { title: "Override" });
+ groupListMap.unshift({ title: "Public" });//, { title: "Override" });
const groupListContents = groupListMap.map(group => {
const groupKey = `acl-${StrCast(group.title)}`;
const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]);
@@ -597,9 +634,10 @@ export class SharingManager extends React.Component<{}> {
{<div className="share-container">
<div className="share-setup">
<Select
- className={"user-search"}
- placeholder={"Enter user or group name..."}
+ className="user-search"
+ placeholder="Enter user or group name..."
isMulti
+ isSearchable
closeMenuOnSelect={false}
options={options}
onChange={this.handleUsersChange}
@@ -623,16 +661,16 @@ export class SharingManager extends React.Component<{}> {
</div>
<div className="acl-container">
- <div className="myDocs-acls">
+ {/* <div className="myDocs-acls">
<input type="checkbox" onChange={action(() => this.myDocAcls = !this.myDocAcls)} checked={this.myDocAcls} /> <label>My Docs</label>
- </div>
+ </div> */}
{Doc.UserDoc().noviceMode ? (null) :
<div className="layoutDoc-acls">
<input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label>
</div>}
- <button className="distribute-button" onClick={() => this.distributeOverCollection()}>
+ {/* <button className="distribute-button" onClick={() => this.distributeOverCollection()}>
Distribute
- </button>
+ </button> */}
</div>
</div>
}
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index 4a89cc69c..5953baec1 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -8,6 +8,8 @@
}
.editableView-container-editing-oneLine {
+ width: 100%;
+
span {
white-space: nowrap;
overflow: hidden;
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index e2f171f9d..f1cecd272 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -47,6 +47,7 @@ export interface EditableProps {
toggle?: () => void;
background?: string | undefined;
placeholder?: string;
+ fullWidth?: boolean; // used in PropertiesView to make the whole key:value input box clickable
}
/**
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 671c0c507..cbaa706e0 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -29,6 +29,7 @@ import { DocumentLinksButton } from "./nodes/DocumentLinksButton";
import { AnchorMenu } from "./pdf/AnchorMenu";
import { SearchBox } from "./search/SearchBox";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { SettingsManager } from "../util/SettingsManager";
const modifiers = ["control", "meta", "shift", "alt"];
type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -135,6 +136,7 @@ export class KeyManager {
// DictationManager.Controls.stop();
GoogleAuthenticationManager.Instance.cancel();
SharingManager.Instance.close();
+ if (!GroupManager.Instance.isOpen) SettingsManager.Instance.close();
GroupManager.Instance.close();
CollectionFreeFormViewChrome.Instance?.clearKeepPrimitiveMode();
window.getSelection()?.empty();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 71bff86c4..2f871c13d 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -45,6 +45,7 @@ import { InkStrokeProperties } from './InkStrokeProperties';
import { LightboxView } from './LightboxView';
import { LinkMenu } from './linking/LinkMenu';
import "./MainView.scss";
+import "./collections/TreeView.scss";
import { AudioBox } from './nodes/AudioBox';
import { DocumentLinksButton } from './nodes/DocumentLinksButton';
import { DocumentView, DocumentViewProps, DocAfterFocusFunc } from './nodes/DocumentView';
@@ -60,7 +61,7 @@ import { AnchorMenu } from './pdf/AnchorMenu';
import { PreviewCursor } from './PreviewCursor';
import { PropertiesView } from './PropertiesView';
import { SearchBox } from './search/SearchBox';
-import { DefaultStyleProvider, StyleProp } from './StyleProvider';
+import { DefaultStyleProvider, DashboardStyleProvider, StyleProp } from './StyleProvider';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -166,7 +167,8 @@ export class MainView extends React.Component {
fa.faWindowMinimize, fa.faWindowRestore, fa.faTextWidth, fa.faTextHeight, fa.faClosedCaptioning, fa.faInfoCircle, fa.faTag, fa.faSyncAlt, fa.faPhotoVideo,
fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical,
fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll,
- fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines);
+ fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines,
+ fa.faSave, fa.faBookmark);
this.initAuthenticationRouters();
}
@@ -177,8 +179,8 @@ export class MainView extends React.Component {
const targClass = targets[0].className.toString();
if (SearchBox.Instance._searchbarOpen || SearchBox.Instance.open) {
const check = targets.some((thing) =>
- (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
- thing.className === "collectionSchema-header-menuOptions"));
+ (thing.className === "collectionSchemaView-searchContainer" || (thing as any)?.dataset.icon === "filter" ||
+ thing.className === "collectionSchema-header-menuOptions"));
!check && SearchBox.Instance.resetSearch(true);
}
!targClass.includes("contextMenu") && ContextMenu.Instance.closeMenu();
diff --git a/src/client/views/PropertiesView.scss b/src/client/views/PropertiesView.scss
index 1365725cb..04934d50b 100644
--- a/src/client/views/PropertiesView.scss
+++ b/src/client/views/PropertiesView.scss
@@ -157,6 +157,66 @@
}
}
+ .propertiesView-filters {
+ //border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-filters-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-filters-title-icon {
+ float: right;
+ justify-items: right;
+ align-items: flex-end;
+ margin-left: auto;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-filters-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+ max-height: 40%;overflow-y: scroll;
+
+ .propertiesView-buttonContainer {
+ float: right;
+ display: flex;
+
+ button {
+ width: 15;
+ height: 15;
+ padding: 0;
+ margin-top: -5;
+ }
+ }
+
+ button {
+ width: 5;
+ height: 5;
+ }
+
+ input {
+ width: 100%;
+ }
+ }
+ }
+
+
.propertiesView-appearance {
//border-bottom: 1px solid black;
//padding: 8.5px;
@@ -332,6 +392,7 @@
}
}
}
+
.propertiesView-fields {
//border-bottom: 1px solid black;
//padding: 8.5px;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 0b2cdde6f..470e51835 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -2,16 +2,16 @@ import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Checkbox, Tooltip } from "@material-ui/core";
import { intersection } from "lodash";
-import { action, computed, observable } from "mobx";
+import { action, autorun, computed, Lambda, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { ColorState, SketchPicker } from "react-color";
-import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, WidthSym } from "../../fields/Doc";
+import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
import { Id } from "../../fields/FieldSymbols";
import { InkField } from "../../fields/InkField";
import { ComputedField } from "../../fields/ScriptField";
import { Cast, NumCast, StrCast } from "../../fields/Types";
import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util";
-import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../Utils";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils";
import { DocumentType } from "../documents/DocumentTypes";
import { DocumentManager } from "../util/DocumentManager";
import { SelectionManager } from "../util/SelectionManager";
@@ -28,7 +28,10 @@ import { PresBox } from "./nodes/PresBox";
import { PropertiesButtons } from "./PropertiesButtons";
import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector";
import "./PropertiesView.scss";
-import { DefaultStyleProvider } from "./StyleProvider";
+import { DefaultStyleProvider, FilteringStyleProvider } from "./StyleProvider";
+import { CurrentUserUtils } from "../util/CurrentUserUtils";
+import { FilterBox } from "./nodes/FilterBox";
+import { List } from "../../fields/List";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -47,14 +50,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed get MAX_EMBED_HEIGHT() { return 200; }
@computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; }
+ @computed get filterDoc() {
+ return FilterBox._filterScope === "Current Collection" ? this.selectedDoc! : CurrentUserUtils.ActiveDashboard;
+ }
@computed get selectedDocumentView() {
if (SelectionManager.Views().length) return SelectionManager.Views()[0];
if (PresBox.Instance?._selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
return undefined;
}
@computed get isPres(): boolean {
- if (this.selectedDoc?.type === DocumentType.PRES) return true;
- return false;
+ return this.selectedDoc?.type === DocumentType.PRES;
}
@computed get dataDoc() { return this.selectedDoc?.[DataSym]; }
@@ -67,6 +72,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable openContexts: boolean = true;
@observable openAppearance: boolean = true;
@observable openTransform: boolean = true;
+ @observable openFilters: boolean = true; // should be false
+
+ /**
+ * autorun to set up the filter doc of a collection if that collection has been selected and the filters panel is open
+ */
+ private selectedDocListenerDisposer: Opt<Lambda>;
+
// @observable selectedUser: string = "";
// @observable addButtonPressed: boolean = false;
@observable layoutDocAcls: boolean = false;
@@ -81,6 +93,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@observable _controlBtn: boolean = false;
@observable _lock: boolean = false;
+ componentDidMount() {
+ this.selectedDocListenerDisposer?.();
+ this.selectedDocListenerDisposer = autorun(() => this.openFilters && this.selectedDoc && this.checkFilterDoc());
+ }
+
+ componentWillUnmount() {
+ this.selectedDocListenerDisposer?.();
+ }
+
@computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
rtfWidth = () => {
@@ -149,6 +170,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
rows.push(<div className="propertiesView-field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
<EditableView
key="editableView"
+ oneLine
contents={"add key:value or #tags"}
height={13}
fontSize={10}
@@ -205,6 +227,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
rows.push(<div className="propertiesView-field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
<EditableView
key="editableView"
+ oneLine
contents={"add key:value or #tags"}
height={13}
fontSize={10}
@@ -398,7 +421,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const showAdmin = effectiveAcls.every(acl => acl === AclAdmin);
// users in common between all docs
- const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym])));
+ const commonKeys: string[] = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym])));
const tableEntries = [];
@@ -415,7 +438,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author);
// shifts the current user, owner, public to the top of the doc.
- tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-"));
+ // tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-"));
tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Public"] === docs[0]["acl-Public"]) ? (AclMap.get(target[AclSym]?.["acl-Public"]) || SharingPermissions.None) : "-multiple-"));
tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame));
if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner"));
@@ -442,13 +465,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const titles = new Set<string>();
SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title)));
const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title);
- return <div className="editable-title"><EditableView
- key="editableView"
- contents={title}
- height={25}
- fontSize={14}
- GetValue={() => title}
- SetValue={this.setTitle} /> </div>;
+ return <div className="editable-title">
+ <EditableView
+ key="editableView"
+ contents={title}
+ height={25}
+ fontSize={14}
+ GetValue={() => title}
+ SetValue={this.setTitle} />
+ </div>;
}
@undoBatch
@@ -512,8 +537,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
}
}
-
-
@computed
get controlPointsButton() {
const formatInstance = InkStrokeProperties.Instance;
@@ -829,6 +852,221 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>;
}
+ @computed get optionsSubMenu() {
+ return <div className="propertiesView-settings" onPointerEnter={action(() => this.inOptions = true)}
+ onPointerLeave={action(() => this.inOptions = false)}>
+ <div className="propertiesView-settings-title"
+ onPointerDown={action(() => this.openOptions = !this.openOptions)}
+ style={{ backgroundColor: this.openOptions ? "black" : "" }}>
+ Options
+ <div className="propertiesView-settings-title-icon">
+ <FontAwesomeIcon icon={this.openOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openOptions ? (null) :
+ <div className="propertiesView-settings-content">
+ <PropertiesButtons />
+ </div>}
+ </div>;
+ }
+
+ @computed get sharingSubMenu() {
+ return <div className="propertiesView-sharing">
+ <div className="propertiesView-sharing-title"
+ onPointerDown={action(() => this.openSharing = !this.openSharing)}
+ style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+ Sharing {"&"} Permissions
+ <div className="propertiesView-sharing-title-icon">
+ <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openSharing ? (null) :
+ <div className="propertiesView-sharing-content">
+ <div className="propertiesView-buttonContainer">
+ {!Doc.UserDoc().noviceMode ? (<div className="propertiesView-acls-checkbox">
+ <Checkbox
+ color="primary"
+ onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)}
+ checked={this.layoutDocAcls}
+ />
+ <div className="propertiesView-acls-checkbox-text">Layout</div>
+ </div>) : (null)}
+ {/* <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
+ <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
+ <FontAwesomeIcon icon="redo-alt" color="white" size="1x" />
+ </button>
+ </Tooltip> */}
+ </div>
+ {this.sharingTable}
+ </div>}
+ </div>;
+ }
+
+ /**
+ * Checks if a currentFilter (FilterDoc) exists on the current collection (if the Properties Panel + Filters submenu are open).
+ * If it doesn't exist, it creates it.
+ */
+ checkFilterDoc() {
+ if (this.filterDoc.type === DocumentType.COL && !this.filterDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.filterDoc);
+ }
+
+ /**
+ * Creates a new currentFilter for this.filterDoc,
+ */
+ createNewFilterDoc = () => {
+ const temp = this.filterDoc._docFilters;
+ this.filterDoc._docFilters = new List<string>();
+ (this.filterDoc.currentFilter as Doc)._docFiltersList = temp;
+ this.filterDoc.currentFilter = undefined;
+ CurrentUserUtils.setupFilterDocs(this.filterDoc);
+ }
+
+ /**
+ * Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter
+ */
+ updateFilterDoc = (doc: Doc) => {
+ if (doc === this.filterDoc.currentFilter) return; // causes problems if you try to reapply the same doc
+ const temp = doc._docFiltersList;
+ const otherTemp = this.filterDoc._docFilters;
+ this.filterDoc._docFilters = new List<string>();
+ (this.filterDoc.currentFilter as Doc)._docFiltersList = otherTemp;
+ this.filterDoc.currentFilter = doc;
+ doc._docFiltersList = new List<string>();
+ this.filterDoc._docFilters = temp;
+ }
+
+ @computed get filtersSubMenu() {
+ return !this.filterDoc?.currentFilter ? (null) : <div className="propertiesView-filters">
+ <div className="propertiesView-filters-title"
+ onPointerDown={action(() => this.openFilters = !this.openFilters)}
+ style={{ backgroundColor: this.openFilters ? "black" : "" }}>
+ Filters
+ <div className="propertiesView-filters-title-icon">
+ <FontAwesomeIcon icon={this.openFilters ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {
+ !this.openFilters ? (null) :
+ <div className="propertiesView-filters-content">
+ <DocumentView
+ Document={this.filterDoc.currentFilter as Doc}
+ DataDoc={undefined}
+ addDocument={undefined}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ rootSelected={returnTrue}
+ removeDocument={returnFalse}
+ ScreenToLocalTransform={this.getTransform}
+ PanelWidth={this.docWidth}
+ PanelHeight={this.docHeight}
+ renderDepth={0}
+ scriptContext={this.filterDoc.currentFilter as Doc}
+ focus={emptyFunction}
+ styleProvider={DefaultStyleProvider}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ docFilters={returnEmptyFilter}
+ docRangeFilters={returnEmptyFilter}
+ searchFilterDocs={returnEmptyDoclist}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ createNewFilterDoc={this.createNewFilterDoc}
+ updateFilterDoc={this.updateFilterDoc}
+ docViewPath={returnEmptyDoclist}
+ layerProvider={undefined}
+ dontCenter="y"
+ />
+ </div>
+ }
+ </div >;
+ }
+
+ @computed get inkSubMenu() {
+ return <>
+ {!this.isInk ? (null) :
+ <div className="propertiesView-appearance">
+ <div className="propertiesView-appearance-title"
+ onPointerDown={action(() => this.openAppearance = !this.openAppearance)}
+ style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
+ Appearance
+ <div className="propertiesView-appearance-title-icon">
+ <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openAppearance ? (null) :
+ <div className="propertiesView-appearance-content">
+ {this.appearanceEditor}
+ </div>}
+ </div>}
+
+ {this.isInk ? <div className="propertiesView-transform">
+ <div className="propertiesView-transform-title"
+ onPointerDown={action(() => this.openTransform = !this.openTransform)}
+ style={{ backgroundColor: this.openTransform ? "black" : "" }}>
+ Transform
+ <div className="propertiesView-transform-title-icon">
+ <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openTransform ? <div className="propertiesView-transform-content">
+ {this.transformEditor}
+ </div> : null}
+ </div> : null}
+ </>;
+ }
+
+ @computed get fieldsSubMenu() {
+ return <div className="propertiesView-fields">
+ <div className="propertiesView-fields-title"
+ onPointerDown={action(() => this.openFields = !this.openFields)}
+ style={{ backgroundColor: this.openFields ? "black" : "" }}>
+ Fields {"&"} Tags
+ <div className="propertiesView-fields-title-icon">
+ <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!Doc.UserDoc().noviceMode && this.openFields ? <div className="propertiesView-fields-checkbox">
+ {this.fieldsCheckbox}
+ <div className="propertiesView-fields-checkbox-text">Layout</div>
+ </div> : null}
+ {!this.openFields ? (null) :
+ <div className="propertiesView-fields-content">
+ {Doc.UserDoc().noviceMode ? this.noviceFields : this.expandedField}
+ </div>}
+ </div>;
+ }
+
+ @computed get contextsSubMenu() {
+ return <div className="propertiesView-contexts">
+ <div className="propertiesView-contexts-title"
+ onPointerDown={action(() => this.openContexts = !this.openContexts)}
+ style={{ backgroundColor: this.openContexts ? "black" : "" }}>
+ Contexts
+ <div className="propertiesView-contexts-title-icon">
+ <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openContexts ? <div className="propertiesView-contexts-content" >{this.contexts}</div> : null}
+ </div>;
+ }
+
+ @computed get layoutSubMenu() {
+ return <div className="propertiesView-layout">
+ <div className="propertiesView-layout-title"
+ onPointerDown={action(() => this.openLayout = !this.openLayout)}
+ style={{ backgroundColor: this.openLayout ? "black" : "" }}>
+ Layout
+ <div className="propertiesView-layout-title-icon">
+ <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openLayout ? <div className="propertiesView-layout-content" >{this.layoutPreview}</div> : null}
+ </div>;
+ }
+
+
+
/**
* Handles adding and removing members from the sharing panel
*/
@@ -849,8 +1087,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
</div>;
} else {
- const novice = Doc.UserDoc().noviceMode;
-
if (this.selectedDoc && !this.isPres) {
return <div className="propertiesView" style={{
width: this.props.width,
@@ -863,121 +1099,19 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
<div className="propertiesView-name">
{this.editableTitle}
</div>
- <div className="propertiesView-settings" onPointerEnter={action(() => this.inOptions = true)}
- onPointerLeave={action(() => this.inOptions = false)}>
- <div className="propertiesView-settings-title"
- onPointerDown={action(() => this.openOptions = !this.openOptions)}
- style={{ backgroundColor: this.openOptions ? "black" : "" }}>
- Options
- <div className="propertiesView-settings-title-icon">
- <FontAwesomeIcon icon={this.openOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {!this.openOptions ? (null) :
- <div className="propertiesView-settings-content">
- <PropertiesButtons />
- </div>}
- </div>
- <div className="propertiesView-sharing">
- <div className="propertiesView-sharing-title"
- onPointerDown={action(() => this.openSharing = !this.openSharing)}
- style={{ backgroundColor: this.openSharing ? "black" : "" }}>
- Sharing {"&"} Permissions
- <div className="propertiesView-sharing-title-icon">
- <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {!this.openSharing ? (null) :
- <div className="propertiesView-sharing-content">
- <div className="propertiesView-buttonContainer">
- {!novice ? (<div className="propertiesView-acls-checkbox">
- <Checkbox
- color="primary"
- onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)}
- checked={this.layoutDocAcls}
- />
- <div className="propertiesView-acls-checkbox-text">Layout</div>
- </div>) : (null)}
- <Tooltip title={<><div className="dash-tooltip">{"Re-distribute sharing settings"}</div></>}>
- <button onPointerDown={() => SharingManager.Instance.distributeOverCollection(this.selectedDoc!)}>
- <FontAwesomeIcon icon="redo-alt" color="white" size="1x" />
- </button>
- </Tooltip>
- </div>
- {this.sharingTable}
- </div>}
- </div>
+ {this.optionsSubMenu}
- {!this.isInk ? (null) :
- <div className="propertiesView-appearance">
- <div className="propertiesView-appearance-title"
- onPointerDown={action(() => this.openAppearance = !this.openAppearance)}
- style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
- Appearance
- <div className="propertiesView-appearance-title-icon">
- <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {!this.openAppearance ? (null) :
- <div className="propertiesView-appearance-content">
- {this.appearanceEditor}
- </div>}
- </div>}
+ {this.sharingSubMenu}
- {this.isInk ? <div className="propertiesView-transform">
- <div className="propertiesView-transform-title"
- onPointerDown={action(() => this.openTransform = !this.openTransform)}
- style={{ backgroundColor: this.openTransform ? "black" : "" }}>
- Transform
- <div className="propertiesView-transform-title-icon">
- <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {this.openTransform ? <div className="propertiesView-transform-content">
- {this.transformEditor}
- </div> : null}
- </div> : null}
+ {this.filtersSubMenu}
- <div className="propertiesView-fields">
- <div className="propertiesView-fields-title"
- onPointerDown={action(() => this.openFields = !this.openFields)}
- style={{ backgroundColor: this.openFields ? "black" : "" }}>
- Fields {"&"} Tags
- <div className="propertiesView-fields-title-icon">
- <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {!novice && this.openFields ? <div className="propertiesView-fields-checkbox">
- {this.fieldsCheckbox}
- <div className="propertiesView-fields-checkbox-text">Layout</div>
- </div> : null}
- {!this.openFields ? (null) :
- <div className="propertiesView-fields-content">
- {novice ? this.noviceFields : this.expandedField}
- </div>}
- </div>
- <div className="propertiesView-contexts">
- <div className="propertiesView-contexts-title"
- onPointerDown={action(() => this.openContexts = !this.openContexts)}
- style={{ backgroundColor: this.openContexts ? "black" : "" }}>
- Contexts
- <div className="propertiesView-contexts-title-icon">
- <FontAwesomeIcon icon={this.openContexts ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {this.openContexts ? <div className="propertiesView-contexts-content" >{this.contexts}</div> : null}
- </div>
- <div className="propertiesView-layout">
- <div className="propertiesView-layout-title"
- onPointerDown={action(() => this.openLayout = !this.openLayout)}
- style={{ backgroundColor: this.openLayout ? "black" : "" }}>
- Layout
- <div className="propertiesView-layout-title-icon">
- <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
- </div>
- </div>
- {this.openLayout ? <div className="propertiesView-layout-content" >{this.layoutPreview}</div> : null}
- </div>
+ {this.inkSubMenu}
+
+ {this.fieldsSubMenu}
+
+ {this.contextsSubMenu}
+
+ {this.layoutSubMenu}
</div>;
}
if (this.isPres) {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index d78446bf6..0460addf3 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -15,6 +15,8 @@ import { MainView } from './MainView';
import { DocumentViewProps, DocumentView } from "./nodes/DocumentView";
import { FieldViewProps } from './nodes/FieldView';
import "./StyleProvider.scss";
+import "./collections/TreeView.scss";
+import "./nodes/FilterBox.scss";
import React = require("react");
import Color = require('color');
@@ -23,6 +25,7 @@ export enum StyleLayers {
}
export enum StyleProp {
+ TreeViewIcon = "treeViewIcon",
DocContents = "docContents", // when specified, the JSX returned will replace the normal rendering of the document view
Opacity = "opacity", // opacity of the document view
Hidden = "hidden", // whether the document view should not be isplayed
@@ -72,6 +75,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
const showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
switch (property.split(":")[0]) {
+ case StyleProp.TreeViewIcon: return doc ? Doc.toIcon(doc) : "question";
case StyleProp.DocContents: return undefined;
case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey";
case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
@@ -167,6 +171,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
if (doc?.type !== DocumentType.INK && layer === true) return "all";
return undefined;
case StyleProp.Decorations:
+ // if (isFooter)
+
if (props?.ContainingCollectionDoc?._viewType === CollectionViewType.Freeform) {
return doc && (isBackground() || selected) && (props?.renderDepth || 0) > 0 &&
((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ?
@@ -178,6 +184,85 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
}
}
+
+function toggleHidden(e: React.MouseEvent, doc: Doc) {
+ UndoManager.RunInBatch(() => runInAction(() => {
+ e.stopPropagation();
+ doc.hidden = doc.hidden ? undefined : true;
+ }), "toggleHidden");
+}
+
+function toggleLock(e: React.MouseEvent, doc: Doc) {
+ UndoManager.RunInBatch(() => runInAction(() => {
+ e.stopPropagation();
+ doc.lockedPosition = doc.lockedPosition ? undefined : true;
+ }), "toggleHidden");
+}
+
+/**
+ * add lock and hide button decorations for the "Dashboards" flyout TreeView
+ */
+export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
+ switch (property.split(":")[0]) {
+ case StyleProp.Decorations:
+ if (doc) {
+ const hidden = doc.hidden;
+ const locked = doc.lockedPosition;
+ return doc._viewType === CollectionViewType.Docking || (Doc.IsSystem(doc) && Doc.UserDoc().noviceMode) ? (null) :
+ <>
+ <div className={`styleProvider-treeView-hide${hidden ? "-active" : ""}`} onClick={(e) => toggleHidden(e, doc)}>
+ <FontAwesomeIcon icon={hidden ? "eye-slash" : "eye"} size="sm" />
+ </div>
+ <div className={`styleProvider-treeView-lock${locked ? "-active" : ""}`} onClick={(e) => toggleLock(e, doc)}>
+ <FontAwesomeIcon icon={locked ? "lock" : "unlock"} size="sm" />
+ </div>
+ </>;
+ }
+ default: return DefaultStyleProvider(doc, props, property);
+
+ }
+}
+
+function changeFilterBool(e: any, doc: Doc) {
+ UndoManager.RunInBatch(() => runInAction(() => {
+ //e.stopPropagation();
+ //doc.lockedPosition = doc.lockedPosition ? undefined : true;
+ }), "changeFilterBool");
+}
+
+function closeFilter(e: React.MouseEvent, doc: Doc) {
+ UndoManager.RunInBatch(() => runInAction(() => {
+ e.stopPropagation();
+ //doc.lockedPosition = doc.lockedPosition ? undefined : true;
+ }), "closeFilter");
+}
+
+
+/**
+ * add (to treeView) for filtering decorations
+ */
+export function FilteringStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
+ switch (property.split(":")[0]) {
+ case StyleProp.Decorations:
+ if (doc) {
+ return doc._viewType === CollectionViewType.Docking || (Doc.IsSystem(doc)) ? (null) :
+ <>
+ <div>
+ <select className="filterBox-treeView-selection" onChange={e => changeFilterBool(e, doc)}>
+ <option value="Is" key="Is">Is</option>
+ <option value="Is Not" key="Is Not">Is Not</option>
+ </select>
+ </div>
+ <div className="filterBox-treeView-close" onClick={(e) => closeFilter(e, doc)}>
+ <FontAwesomeIcon icon={"times"} size="sm" />
+ </div>
+ </>;
+ }
+ default: return DefaultStyleProvider(doc, props, property);
+
+ }
+}
+
//
// a preliminary semantic-"layering/grouping" mechanism for determining interactive properties of documents
// currently, the provider tests whether the docuemnt's layer field matches the activeLayer field of the tab.
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 2324c0eb4..28f4f76be 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -82,7 +82,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
return Cast(this.dataField, listSpec(Doc));
}
docFilters = () => {
- return [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])];
+ return [...this.props.docFilters(), ...Cast(FilterBox._filterScope === "Current Collection" ? this.props.Document._docFilters : CurrentUserUtils.ActiveDashboard._docFilters, listSpec("string"), [])];
}
docRangeFilters = () => {
return [...this.props.docRangeFilters(), ...Cast(this.props.Document._docRangeFilters, listSpec("string"), [])];
@@ -114,10 +114,15 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one
}
+ // console.log(CurrentUserUtils.ActiveDashboard._docFilters);
+ if (!this.props.Document._docFilters && this.props.Document.currentFilter) {
+ (this.props.Document.currentFilter as Doc).filterBoolean = (this.props.ContainingCollectionDoc?.currentFilter as Doc)?.filterBoolean;
+ }
const docsforFilter: Doc[] = [];
+ console.log(this.props.Document.system);
childDocs.forEach((d) => {
- if (DocUtils.Excluded(d, docFilters)) return;
- let notFiltered = d.z || ((!searchDocs.length || searchDocs.includes(d)) && (DocUtils.FilterDocs([d], docFilters, docRangeFilters, viewSpecScript).length > 0));
+ // if (DocUtils.Excluded(d, docFilters)) return;
+ let notFiltered = d.z || ((!searchDocs.length || searchDocs.includes(d)) && (DocUtils.FilterDocs([d], docFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0));
const fieldKey = Doc.LayoutFieldKey(d);
const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView");
const data = d[annos ? fieldKey + "-annotations" : fieldKey];
@@ -125,13 +130,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
let subDocs = DocListCast(data);
if (subDocs.length > 0) {
let newarray: Doc[] = [];
- notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, docFilters, docRangeFilters, viewSpecScript).length);
+ notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, docFilters, docRangeFilters, viewSpecScript, this.props.Document).length);
while (subDocs.length > 0 && !notFiltered) {
newarray = [];
subDocs.forEach((t) => {
const fieldKey = Doc.LayoutFieldKey(t);
const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView");
- notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!docFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], docFilters, docRangeFilters, viewSpecScript).length));
+ notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!docFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], docFilters, docRangeFilters, viewSpecScript, this.props.Document).length));
DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc));
});
subDocs = newarray;
@@ -481,4 +486,5 @@ import { SelectionManager } from "../../util/SelectionManager";
import { OverlayView } from "../OverlayView";
import { Hypothesis } from "../../util/HypothesisUtils";
import { GetEffectiveAcl } from "../../../fields/util";
+import { FilterBox } from "../nodes/FilterBox";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 525fa8ca1..f5eb534aa 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -35,7 +35,8 @@ export type collectionTreeViewProps = {
};
@observer
-export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) {
+export class
+ CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) {
private treedropDisposer?: DragManager.DragDropDisposer;
private _isChildActive = false;
private _mainEle?: HTMLDivElement;
@@ -222,7 +223,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
render() {
TraceMobx();
if (!(this.doc instanceof Doc)) return (null);
- const background = this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
+ const background = this.props.treeViewHideTitle && this.props.treeViewHideHeaderFields ? "#9F9F9F" : this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor);
const paddingX = `${NumCast(this.doc._xPadding, 15)}px`;
const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`;
const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 535bf539e..1e693f594 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -38,6 +38,8 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
+import { FilterBox } from '../nodes/FilterBox';
+import { listSpec } from '../../../fields/Schema';
export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
@@ -117,6 +119,19 @@ export class CollectionView extends Touchable<CollectionViewProps> {
whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive);
+ /**
+ * Applies the collection/dashboard's current filter attributes to the doc being added
+ */
+ addFilterAttributes = (doc: Doc) => {
+ Cast((FilterBox._filterScope === "Current Collection" ? this.props.Document : CurrentUserUtils.ActiveDashboard)._docFilters, listSpec("string"))?.forEach(attribute => {
+ if (attribute.charAt(0).toUpperCase() === attribute.charAt(0)) {
+ const fields = attribute.split(':');
+ if (fields[2] === "check") doc[DataSym][fields[0]] = fields[1];
+ else if (fields[2] === "x" && doc[DataSym][fields[0]] === fields[1]) doc[DataSym][fields[0]] = undefined;
+ }
+ });
+ }
+
@action.bound
addDocument = (doc: Doc | Doc[]): boolean => {
if (this.props.filterAddDocument?.(doc) === false) {
@@ -158,6 +173,7 @@ export class CollectionView extends Touchable<CollectionViewProps> {
DocUtils.LeavePushpin(doc);
doc._stayInCollection = undefined;
doc.context = this.props.Document;
+ this.addFilterAttributes(doc); //
});
added.map(doc => this.props.layerProvider?.(doc, true));// assigns layer values to the newly added document... testing the utility of this
(targetDataDoc[this.props.fieldKey] as List<Doc>).push(...added);
diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss
index 067675038..2f74a49bb 100644
--- a/src/client/views/collections/TreeView.scss
+++ b/src/client/views/collections/TreeView.scss
@@ -8,6 +8,7 @@
width: 100%;
overflow: hidden;
}
+
.treeView-container,
.treeView-container-active {
.bullet-outline {
@@ -20,21 +21,26 @@
.treeView-bulletIcons {
width: 15px;
+
.treeView-expandIcon {
display: none;
left: -10px;
position: absolute;
}
+
.treeView-checkIcon {
- left: -10px;
+ left: 3.5px;
+ top: 2px;
position: absolute;
}
+
&:hover {
.treeView-expandIcon {
display: unset;
}
}
}
+
.bullet {
position: relative;
width: $TREE_BULLET_WIDTH;
@@ -45,9 +51,11 @@
border-radius: 4px;
}
}
+
.treeView-container-active {
z-index: 100;
position: relative;
+
.formattedTextbox-sidebar {
background-color: #ffff001f !important;
height: 500px !important;
@@ -70,7 +78,8 @@
display: flex;
overflow: hidden;
}
-.treeView-border{
+
+.treeView-border {
border-left: dashed 1px #00000042;
}
@@ -78,15 +87,20 @@
.treeView-header {
border: transparent 1px solid;
display: flex;
+
//align-items: center;
::-webkit-scrollbar {
- display: none;
+ display: none;
}
+
.formattedTextBox-cont {
- .formattedTextbox-sidebar, .formattedTextbox-sidebar-inking {
+
+ .formattedTextbox-sidebar,
+ .formattedTextbox-sidebar-inking {
overflow: visible !important;
border-left: unset;
}
+
overflow: visible !important;
}
@@ -104,12 +118,18 @@
margin-left: 0.25rem;
opacity: 0.75;
- >svg, .styleProvider-treeView-lock, .styleProvider-treeView-hide, .styleProvider-treeView-lock-active, .styleProvider-treeView-hide-active {
+ >svg,
+ .styleProvider-treeView-lock,
+ .styleProvider-treeView-hide,
+ .styleProvider-treeView-lock-active,
+ .styleProvider-treeView-hide-active {
margin-left: 0.25rem;
- margin-right: 0.25rem;
+ margin-right: 0.25rem;
}
-
- >svg, .styleProvider-treeView-lock, .styleProvider-treeView-hide {
+
+ >svg,
+ .styleProvider-treeView-lock,
+ .styleProvider-treeView-hide {
display: none;
}
}
@@ -134,7 +154,10 @@
}
.right-buttons-container {
- >svg, .styleProvider-treeView-lock, .styleProvider-treeView-hide {
+
+ >svg,
+ .styleProvider-treeView-lock,
+ .styleProvider-treeView-hide {
display: inherit;
}
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index 0d7ccc4bd..1d14c63c7 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -470,7 +470,6 @@ export class TreeView extends React.Component<TreeViewProps> {
}
</div>;
}
-
@computed get headerElements() {
return (Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode) || this.props.treeViewHideHeaderFields() ? (null) :
<>
@@ -627,7 +626,7 @@ export class TreeView extends React.Component<TreeViewProps> {
{view}
</div >
<div className={"right-buttons-container"}>
- {this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations + (Doc.IsSystem(this.props.containingCollection) ? ":afterHeader" : ""))} {/* hide and lock buttons */}
+ {this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations)} {/* hide and lock buttons */}
{this.headerElements}
</div>
</>;
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 749ffa7fd..36572b861 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -3,6 +3,7 @@
.documentView-effectsWrapper {
border-radius: inherit;
}
+
.documentView-node,
.documentView-node-topmost {
position: inherit;
@@ -37,12 +38,13 @@
overflow-y: scroll;
height: calc(100% - 20px);
}
+
.documentView-linkAnchorBoxAnchor {
- display:flex;
+ display: flex;
overflow: hidden;
.documentView-node {
- width:10px !important;
+ width: 10px !important;
}
}
@@ -64,6 +66,7 @@
top: 15%;
}
}
+
.documentView-treeView {
max-height: 1.5em;
text-overflow: ellipsis;
@@ -71,7 +74,8 @@
white-space: pre;
width: 100%;
overflow: hidden;
- > .documentView-node {
+
+ >.documentView-node {
position: absolute;
}
}
@@ -80,14 +84,33 @@
border-radius: inherit;
width: 100%;
height: 100%;
+
+ .sharingIndicator {
+ height: 30px;
+ width: 30px;
+ border-radius: 50%;
+ position: absolute;
+ right: -15;
+ opacity: 0.9;
+ pointer-events: auto;
+ background-color: #9dca96;
+ letter-spacing: 2px;
+ font-size: 10px;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ }
}
.documentView-anchorCont {
position: absolute;
- top: 0;
- left: 0;
+ top: 0;
+ left: 0;
width: 100%;
- height: 100%;
+ height: 100%;
display: inline-block;
pointer-events: none;
}
@@ -100,6 +123,7 @@
top: 0;
left: 0;
}
+
.documentView-styleWrapper {
position: absolute;
display: inline-block;
@@ -113,7 +137,8 @@
position: absolute;
}
- .documentView-titleWrapper, .documentView-titleWrapper-hover {
+ .documentView-titleWrapper,
+ .documentView-titleWrapper-hover {
overflow: hidden;
color: white;
transform-origin: top left;
@@ -126,8 +151,9 @@
white-space: pre;
position: absolute;
}
+
.documentView-titleWrapper-hover {
- display:none;
+ display: none;
}
.documentView-searchHighlight {
@@ -150,18 +176,21 @@
}
-.documentView-node:hover, .documentView-node-topmost:hover {
- > .documentView-styleWrapper {
- > .documentView-titleWrapper-hover {
- display:inline-block;
+.documentView-node:hover,
+.documentView-node-topmost:hover {
+ >.documentView-styleWrapper {
+ >.documentView-titleWrapper-hover {
+ display: inline-block;
}
}
- > .documentView-styleWrapper {
- > .documentView-captionWrapper {
+
+ >.documentView-styleWrapper {
+ >.documentView-captionWrapper {
opacity: 1;
}
}
}
+
.contentFittingDocumentView {
position: relative;
display: flex;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 80831708d..231c9ff38 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,7 +10,7 @@ import { listSpec } from "../../../fields/Schema";
import { ScriptField } from '../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types";
import { AudioField } from "../../../fields/URLField";
-import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
+import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
import { MobileInterface } from '../../../mobile/MobileInterface';
import { emptyFunction, hasDescendantTarget, OmitKeys, returnFalse, returnVal, Utils } from "../../../Utils";
import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils';
@@ -123,6 +123,8 @@ export interface DocumentViewSharedProps {
cantBrush?: boolean; // whether the document doesn't show brush highlighting
pointerEvents?: string;
scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document
+ createNewFilterDoc?: () => void;
+ updateFilterDoc?: (doc: Doc) => void;
}
export interface DocumentViewProps extends DocumentViewSharedProps {
// properties specific to DocumentViews but not to FieldView
@@ -605,7 +607,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.Document === CurrentUserUtils.ActiveDashboard) {
alert((e.target as any)?.closest?.("*.lm_content") ?
"You can't perform this move most likely because you don't have permission to modify the destination." :
- "linking to document tabs not yet supported. Drop link on document content.");
+ "Linking to document tabs not yet supported. Drop link on document content.");
return;
}
const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData;
@@ -801,6 +803,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>;
}
+ get indicatorIcon() {
+ if (this.props.Document["acl-Public"] !== SharingPermissions.None) return "globe-americas";
+ else if (this.props.Document.numGroupsShared || NumCast(this.props.Document.numUsersShared, 0) > 1) return "users";
+ else return "user";
+ }
+
@undoBatch
hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
anchorPanelWidth = () => this.props.PanelWidth() || 1;
diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss
index d32cc0d2b..4fe1fa2eb 100644
--- a/src/client/views/nodes/FilterBox.scss
+++ b/src/client/views/nodes/FilterBox.scss
@@ -1,17 +1,155 @@
-
-
.filterBox-flyout {
- width: 400px;
display: block;
text-align: left;
+ font-weight: 100;
+
.filterBox-flyout-facet {
- background-color: lightgray;
- text-align: left;
- display: inline-block;
- position: relative;
- width: 100%;
+ background-color: white;
+ text-align: left;
+ display: inline-block;
+ position: relative;
+ width: 100%;
+
+ .filterBox-flyout-facet-check {
+ margin-right: 6px;
+ }
+ }
+}
+
+
+.filter-bookmark {
+ //display: flex;
+
+ .filter-bookmark-icon {
+ float: right;
+ margin-right: 10px;
+ margin-top: 7px;
}
}
+
+// .filterBox-bottom {
+// // position: fixed;
+// // bottom: 0;
+// // width: 100%;
+// }
+
+.filterBox-select {
+ // width: 90%;
+ margin-top: 5px;
+ // margin-bottom: 15px;
+}
+
+
+.filterBox-saveBookmark {
+ background-color: #e9e9e9;
+ border-radius: 11px;
+ padding-left: 8px;
+ padding-right: 8px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ margin: 8px;
+ display: flex;
+ font-size: 11px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: white;
+ }
+
+ .filterBox-saveBookmark-icon {
+ margin-right: 6px;
+ margin-top: 4px;
+ margin-left: 2px;
+ }
+
+}
+
+.filterBox-select-scope,
+.filterBox-select-bool,
+.filterBox-addWrapper,
+.filterBox-select-matched,
+.filterBox-saveWrapper {
+ font-size: 10px;
+ justify-content: center;
+ justify-items: center;
+ padding-bottom: 10px;
+ display: flex;
+}
+
+.filterBox-addWrapper {
+ font-size: 11px;
+ width: 100%;
+}
+
+.filterBox-saveWrapper {
+ width: 100%;
+}
+
+// .filterBox-top {
+// padding-bottom: 20px;
+// border-bottom: 2px solid black;
+// position: fixed;
+// top: 0;
+// width: 100%;
+// }
+
+.filterBox-select-scope {
+ padding-bottom: 20px;
+ border-bottom: 2px solid black;
+}
+
+.filterBox-title {
+ font-size: 15;
+ // border: 2px solid black;
+ width: 100%;
+ align-self: center;
+ text-align: center;
+ background-color: #d3d3d3;
+}
+
+.filterBox-select-bool {
+ margin-top: 6px;
+}
+
+.filterBox-select-text {
+ margin-right: 8px;
+ margin-left: 8px;
+ margin-top: 3px;
+}
+
+.filterBox-select-box {
+ margin-right: 2px;
+ font-size: 30px;
+ border: 0;
+ background: transparent;
+}
+
+.filterBox-selection {
+ border-radius: 6px;
+ border: none;
+ background-color: #e9e9e9;
+ padding: 2px;
+
+ &:hover {
+ background-color: white;
+ }
+}
+
+.filterBox-addFilter {
+ width: 120px;
+ background-color: #e9e9e9;
+ border-radius: 12px;
+ padding: 5px;
+ margin: 5px;
+ display: flex;
+ text-align: center;
+ justify-content: center;
+
+ &:hover {
+ background-color: white;
+ }
+}
+
.filterBox-treeView {
display: flex;
flex-direction: column;
@@ -20,24 +158,19 @@
position: absolute;
right: 0;
top: 0;
- border-left: solid 1px;
z-index: 1;
+ background-color: #9F9F9F;
.filterBox-addfacet {
display: inline-block;
width: 200px;
height: 30px;
- background: darkGray;
text-align: left;
.filterBox-addFacetButton {
display: flex;
margin: auto;
cursor: pointer;
-
- .filterBox-span {
- margin-right: 15px;
- }
}
>div,
@@ -50,6 +183,7 @@
.filterBox-tree {
display: inline-block;
width: 100%;
- height: calc(100% - 30px);
+ margin-bottom: 10px;
+ //height: calc(100% - 30px);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx
index 34986e87a..b9a981e77 100644
--- a/src/client/views/nodes/FilterBox.tsx
+++ b/src/client/views/nodes/FilterBox.tsx
@@ -8,8 +8,8 @@ import { List } from "../../../fields/List";
import { RichTextField } from "../../../fields/RichTextField";
import { listSpec, makeInterface } from "../../../fields/Schema";
import { ComputedField, ScriptField } from "../../../fields/ScriptField";
-import { Cast } from "../../../fields/Types";
-import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero, returnTrue } from "../../../Utils";
+import { Cast, StrCast } from "../../../fields/Types";
+import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils";
import { Docs } from "../../documents/Documents";
import { DocumentType } from "../../documents/DocumentTypes";
import { CollectionDockingView } from "../collections/CollectionDockingView";
@@ -19,17 +19,75 @@ import { SearchBox } from "../search/SearchBox";
import { FieldView, FieldViewProps } from './FieldView';
import './FilterBox.scss';
import { Scripting } from "../../util/Scripting";
+import { SelectionManager } from "../../util/SelectionManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
+import Select from "react-select";
+import { UserOptions } from "../../util/GroupManager";
+import { DocumentViewProps } from "./DocumentView";
+import { DefaultStyleProvider, StyleProp } from "../StyleProvider";
+import { CollectionViewType } from "../collections/CollectionView";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { EditableView } from "../EditableView";
type FilterBoxDocument = makeInterface<[typeof documentSchema]>;
const FilterBoxDocument = makeInterface(documentSchema);
@observer
export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDocument>(FilterBoxDocument) {
+
+ constructor(props: Readonly<FieldViewProps>) {
+ super(props);
+ const targetDoc = FilterBox.targetDoc;
+ if (targetDoc && !targetDoc.currentFilter) CurrentUserUtils.setupFilterDocs(targetDoc);
+ }
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); }
+ @observable static _filterScope = "Current Dashboard";
+ public _filterSelected = false;
+ public _filterMatch = "matched";
+
+ @computed static get currentContainingCollectionDoc() {
+ let docView: any = SelectionManager.Views()[0];
+ let doc = docView.Document;
+
+ while (docView && doc && doc.type !== "collection") {
+ doc = docView.props.ContainingCollectionDoc;
+ docView = docView.props.ContainingCollectionView;
+ }
+
+ return doc;
+ }
+
+ // /**
+ // * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
+ // */
+
+
+ // trying to get this to work rather than the version below this
+
+ // @computed static get targetDoc() {
+ // console.log(FilterBox.currentContainingCollectionDoc.type);
+ // if (FilterBox._filterScope === "Current Collection") {
+ // return FilterBox.currentContainingCollectionDoc;
+ // }
+ // else return CollectionDockingView.Instance.props.Document;
+ // // return FilterBox._filterScope === "Current Collection" ? SelectionManager.Views()[0].Document || CollectionDockingView.Instance.props.Document : CollectionDockingView.Instance.props.Document;
+ // }
+
+
+ /**
+ * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
+ */
+ @computed static get targetDoc() {
+ console.log("recomputing");
+ if (FilterBox._filterScope === "Current Collection") {
+ return SelectionManager.Views()[0]?.Document.type === "collection" ? SelectionManager.Views()[0].Document : SelectionManager.Views()[0]?.props.ContainingCollectionDoc!;
+ }
+ else return CurrentUserUtils.ActiveDashboard;
+ }
+
@observable _loaded = false;
componentDidMount() {
reaction(() => DocListCastAsync(CollectionDockingView.Instance.props.Document.data),
@@ -40,18 +98,19 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
}, { fireImmediately: true });
}
@computed get allDocs() {
- trace();
+ // trace();
const allDocs = new Set<Doc>();
- if (this._loaded && CollectionDockingView.Instance) {
- const activeTabs = DocListCast(CollectionDockingView.Instance.props.Document.data);
+ const targetDoc = FilterBox.targetDoc;
+ if (this._loaded && targetDoc) {
+ const activeTabs = DocListCast(targetDoc.data);
SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc));
- setTimeout(() => CollectionDockingView.Instance.props.Document.allDocuments = new List<Doc>(Array.from(allDocs)));
+ setTimeout(() => targetDoc.allDocuments = new List<Doc>(Array.from(allDocs)));
}
return allDocs;
}
@computed get _allFacets() {
- trace();
+ // trace();
const noviceReqFields = ["author", "tags", "text", "type"];
const noviceLayoutFields: string[] = [];//["_curPage"];
const noviceFields = [...noviceReqFields, ...noviceLayoutFields];
@@ -61,6 +120,21 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.UserDoc().noviceMode).sort();
}
+
+ /**
+ * The current attributes selected to filter based on
+ */
+ @computed get activeAttributes() {
+ return DocListCast(this.dataDoc[this.props.fieldKey]);
+ }
+
+ /**
+ * @returns a string array of the current attributes
+ */
+ @computed get currentFacets() {
+ return this.activeAttributes.map(attribute => StrCast(attribute.title));
+ }
+
gatherFieldValues(dashboard: Doc, facetKey: string) {
const childDocs = DocListCast((dashboard.data as any)[0].data);
const valueSet = new Set<string>();
@@ -81,7 +155,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
subDocs.forEach((t) => {
const facetVal = t[facetKey];
if (facetVal instanceof RichTextField) rtFields++;
- facetVal && valueSet.add(Field.toString(facetVal as Field));
+ facetVal !== undefined && valueSet.add(Field.toString(facetVal as Field));
const fieldKey = Doc.LayoutFieldKey(t);
const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView");
DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc));
@@ -93,13 +167,43 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
});
return { strings: Array.from(valueSet.keys()), rtFields };
}
+
+ public removeFilter = (filterName: string) => {
+ console.log("remove filter");
+ const targetDoc = FilterBox.targetDoc;
+ const filterDoc = targetDoc.currentFilter as Doc;
+ const attributes = DocListCast(filterDoc["data"]);
+ const found = attributes.findIndex(doc => doc.title === filterName);
+ if (found !== -1) {
+ (filterDoc["data"] as List<Doc>).splice(found, 1);
+ const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
+ docFilter.splice(index, 1);
+ }
+ }
+ const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string"));
+ if (docRangeFilters) {
+ let index: number;
+ while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) {
+ docRangeFilters.splice(index, 3);
+ }
+ }
+ }
+ }
+
/**
* Responds to clicking the check box in the flyout menu
*/
facetClick = (facetHeader: string) => {
- const targetDoc = CollectionDockingView.Instance.props.Document;
- const found = DocListCast(this.dataDoc[this.props.fieldKey]).findIndex(doc => doc.title === facetHeader);
+
+ console.log("facetClick: " + facetHeader);
+ console.log(this.props.fieldKey);
+ const targetDoc = FilterBox.targetDoc;
+ const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader);
if (found !== -1) {
+ // comment this bit out later once the x works in treeview
(this.dataDoc[this.props.fieldKey] as List<Doc>).splice(found, 1);
const docFilter = Cast(targetDoc._docFilters, listSpec("string"));
if (docFilter) {
@@ -137,6 +241,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
newFacet._textBoxPadding = 4;
const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`;
newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" });
+ // function foo (this:doc, text:string)
+ // "this" should be the item called noot (maybe) or the treeview doc itself
} else if (facetHeader !== "tags" && nonNumbers / facetValues.strings.length < .1) {
newFacet = Docs.Create.SliderDocument({ title: facetHeader, _overflow: "visible", _fitWidth: true, _height: 40, _stayInCollection: true, _hideContextMenu: true, treeViewExpandedView: "layout", treeViewOpen: true });
const newFacetField = Doc.LayoutFieldKey(newFacet);
@@ -156,6 +262,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
newFacet.title = facetHeader;
newFacet.treeViewOpen = true;
newFacet.type = DocumentType.COL;
+ // const capturedVariables = { layoutDoc: targetDoc, system: true, _stayInCollection: true, _hideContextMenu: true, dataDoc: (targetDoc.data as any)[0][DataSym] };
+ // newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables);
newFacet.target = targetDoc;
newFacet.data = ComputedField.MakeFunction(`readFacetData(self.target, "${facetHeader}")`);
}
@@ -168,28 +276,139 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
return script ? () => script : undefined;
}
+
+ /**
+ * Sets whether filters are ANDed or ORed together
+ */
+ @action
+ changeBool = (e: any) => {
+ (FilterBox.targetDoc.currentFilter as Doc).filterBoolean = e.currentTarget.value;
+ }
+
+ /**
+ * Changes the value of the variable that determines whether the filters should apply to the dashboard or the collection
+ */
+ @action
+ changeScope = (e: any) => {
+ FilterBox._filterScope = e.currentTarget.value;
+ }
+
+ /**
+ * Changes whether to select matched or unmatched documents
+ */
+ @action
+ changeMatch = (e: any) => {
+ this._filterMatch = e.currentTarget.value;
+ }
+
+ @action
+ changeSelected = () => {
+ if (this._filterSelected) {
+ this._filterSelected = false;
+ SelectionManager.DeselectAll();
+ } else {
+ this._filterSelected = true;
+ // helper method to select specified docs
+ }
+ }
+
+ FilteringStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) {
+ switch (property.split(":")[0]) {
+ case StyleProp.Decorations:
+ if (doc) {
+ return doc._viewType === CollectionViewType.Docking || (Doc.IsSystem(doc)) ? (null) :
+ <>
+ <div style={{ marginRight: "5px", fontSize: "10px" }}>
+ <select className="filterBox-selection">
+ <option value="Is" key="Is">Is</option>
+ <option value="Is Not" key="Is Not">Is Not</option>
+ </select>
+ </div>
+ <div className="filterBox-treeView-close" onClick={e => this.removeFilter(StrCast(doc.title))}>
+ <FontAwesomeIcon icon={"times"} size="sm" />
+ </div>
+ </>;
+ }
+ default: return DefaultStyleProvider(doc, props, property);
+
+ }
+ }
+
suppressChildClick = () => ScriptField.MakeScript("")!;
- @computed get flyoutpanel() {
- return <div className="filterBox-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}>
- {this._allFacets.map(facet => <label className="filterBox-flyout-facet" key={`${facet}`} onClick={e => this.facetClick(facet)}>
- <input type="checkbox" onChange={emptyFunction} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} />
- <span className="checkmark" />
- {facet}
- </label>)}
- </div>;
+
+ /**
+ * Adds a filterDoc to the list of saved filters
+ */
+ saveFilter = () => {
+ Doc.AddDocToList(Doc.UserDoc(), "savedFilters", this.props.Document);
+ }
+
+ /**
+ * Changes the title of the filterDoc
+ */
+ onTitleValueChange = (val: string) => {
+ this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc.title}`;
+ return true;
+ }
+
+ /**
+ * The flyout from which you can select a saved filter to apply
+ */
+ @computed get flyoutPanel() {
+ return DocListCast(Doc.UserDoc().savedFilters).map(doc => {
+ return <>
+ <div className="filterBox-tempFlyout" onWheel={e => e.stopPropagation()} style={{ height: 50, border: "2px" }} onPointerDown={() => this.props.updateFilterDoc?.(doc)}>
+ {StrCast(doc.title)}
+ </div>
+ </>;
+ }
+ );
}
+
render() {
const facetCollection = this.props.Document;
+ // TODO uncomment the line below when the treeview x works
+ // const options = this._allFacets.filter(facet => this.currentFacets.indexOf(facet) === -1).map(facet => ({ value: facet, label: facet }));
+ const options = this._allFacets.map(facet => ({ value: facet, label: facet }));
+ console.log(this.props.PanelHeight());
return this.props.dontRegisterView ? (null) : <div className="filterBox-treeView" style={{ width: "100%" }}>
- <div className="filterBox-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}>
- <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyoutpanel}>
- <div className="filterBox-addFacetButton">
- <FontAwesomeIcon icon={"edit"} size={"lg"} />
- <span className="filterBox-span">Choose Facets</span>
- </div>
- </Flyout>
+
+ <div className="filterBox-title">
+ <EditableView
+ key="editableView"
+ contents={this.props.Document.title}
+ height={24}
+ fontSize={15}
+ GetValue={() => StrCast(this.props.Document.title)}
+ SetValue={this.onTitleValueChange}
+ />
+ </div>
+
+ <div className="filterBox-select-bool">
+ <select className="filterBox-selection" onChange={this.changeBool}>
+ <option value="AND" key="AND" selected={(FilterBox.targetDoc.currentFilter as Doc).filterBoolean === "AND"}>AND</option>
+ <option value="OR" key="OR" selected={(FilterBox.targetDoc.currentFilter as Doc).filterBoolean === "OR"}>OR</option>
+ </select>
+ <div className="filterBox-select-text">filters in </div>
+ <select className="filterBox-selection" onChange={this.changeScope}>
+ <option value="Current Dashboard" key="Current Dashboard" selected={"Current Dashboard" === FilterBox._filterScope}>Current Dashboard</option>
+ {/* <option value="Current Tab" key="Current Tab">Current Tab</option> */}
+ <option value="Current Collection" key="Current Collection" selected={"Current Collection" === FilterBox._filterScope}>Current Collection</option>
+ </select>
</div>
+
+ <div className="filterBox-select">
+ <Select
+ placeholder="Add a filter..."
+ options={options}
+ isMulti={false}
+ onChange={val => this.facetClick((val as UserOptions).value)}
+ value={null}
+ closeMenuOnSelect={false}
+ />
+ </div>
+
<div className="filterBox-tree" key="tree">
<CollectionTreeView
Document={facetCollection}
@@ -231,6 +450,42 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc
removeDocument={returnFalse}
addDocument={returnFalse} />
</div>
+ <div className="filterBox-bottom">
+ {/* <div className="filterBox-select-matched">
+ <input className="filterBox-select-box" type="checkbox"
+ onChange={this.changeSelected} />
+ <div className="filterBox-select-text">select</div>
+ <select className="filterBox-selection" onChange={e => this.changeMatch(e)}>
+ <option value="matched" key="matched">matched</option>
+ <option value="unmatched" key="unmatched">unmatched</option>
+ </select>
+ <div className="filterBox-select-text">documents</div>
+ </div> */}
+
+ <div style={{ display: "flex" }}>
+ <div className="filterBox-saveWrapper">
+ <div className="filterBox-saveBookmark"
+ onPointerDown={this.saveFilter}
+ >
+ <div>SAVE</div>
+ </div>
+ </div>
+ <div className="filterBox-saveWrapper">
+ <div className="filterBox-saveBookmark">
+ <Flyout className="myFilters-flyout" anchorPoint={anchorPoints.TOP} content={this.flyoutPanel}>
+ <div>FILTERS</div>
+ </Flyout>
+ </div>
+ </div>
+ <div className="filterBox-saveWrapper">
+ <div className="filterBox-saveBookmark"
+ onPointerDown={this.props.createNewFilterDoc}
+ >
+ <div>NEW</div>
+ </div>
+ </div>
+ </div>
+ </div>
</div>;
}
}
@@ -245,7 +500,7 @@ Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader:
}
return undefined;
});
-Scripting.addGlobal(function readFacetData(targetDoc: Doc, facetHeader: string) {
+Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) {
const allCollectionDocs = DocListCast(CollectionDockingView.Instance?.props.Document.allDocuments);
const set = new Set<string>();
if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key)));
@@ -258,7 +513,7 @@ Scripting.addGlobal(function readFacetData(targetDoc: Doc, facetHeader: string)
const doc = new Doc();
doc.system = true;
doc.title = facetValue.toString();
- doc.target = targetDoc;
+ doc.target = layoutDoc;
doc.facetHeader = facetHeader;
doc.facetValue = facetValue;
doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(self.target, self.facetHeader, self.facetValue)");
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index d0ccce9cf..31043f5be 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -9,7 +9,6 @@ import { Scripting, scriptingGlobal } from "../client/util/Scripting";
import { SelectionManager } from "../client/util/SelectionManager";
import { afterDocDeserialize, autoObject, Deserializable, SerializationHelper } from "../client/util/SerializationHelper";
import { UndoManager } from "../client/util/UndoManager";
-import { CollectionDockingView } from "../client/views/collections/CollectionDockingView";
import { intersectRect, Utils } from "../Utils";
import { DateField } from "./DateField";
import { Copy, HandleUpdate, Id, OnUpdate, Parent, Self, SelfProxy, ToScriptString, ToString, Update } from "./FieldSymbols";
@@ -1049,8 +1048,7 @@ export namespace Doc {
prevLayout === "icon" && (doc.deiconifyLayout = undefined);
doc.layoutKey = deiconify || "layout";
}
- export function setDocFilterRange(target: Doc, key: string, range?: number[]) {
- const container = target ?? CollectionDockingView.Instance.props.Document;
+ export function setDocFilterRange(container: Doc, key: string, range?: number[]) {
const docRangeFilters = Cast(container._docRangeFilters, listSpec("string"), []);
for (let i = 0; i < docRangeFilters.length; i += 3) {
if (docRangeFilters[i] === key) {
@@ -1069,9 +1067,9 @@ 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(target: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x", toggle?: boolean, fieldPrefix?: string, append: boolean = true) {
- const container = target ?? CollectionDockingView.Instance.props.Document;
- const filterField = "_" + (fieldPrefix ? fieldPrefix + "-" : "") + "docFilters";
+ export function setDocFilter(container: Opt<Doc>, key: string, value: any, modifiers: "remove" | "match" | "check" | "x", toggle?: boolean, fieldSuffix?: string) {
+ if (!container) return;
+ const filterField = "_" + (fieldSuffix ? fieldSuffix + "-" : "") + "docFilters";
const docFilters = Cast(container[filterField], listSpec("string"), []);
runInAction(() => {
for (let i = 0; i < docFilters.length; i++) {