diff options
70 files changed, 2116 insertions, 2577 deletions
diff --git a/package-lock.json b/package-lock.json index da700cee2..19f10bb63 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1102,15 +1102,6 @@ "@types/prosemirror-transform": "*" } }, - "@types/puppeteer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-2.1.1.tgz", - "integrity": "sha512-FqPZvUtnpTGrqbHvPUn76pvVcBPEVEqZftrdOjr6YRkaaxkjKQ8dQLNaQBjER7Lvd1Q6+0R0XR+N3tYGWBSzNw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/qs": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", diff --git a/package.json b/package.json index 7ae7cdc95..47aea960f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "@types/prosemirror-state": "^1.2.4", "@types/prosemirror-transform": "^1.1.1", "@types/prosemirror-view": "^1.11.2", - "@types/puppeteer": "^2.1.1", "@types/rc-switch": "^1.9.0", "@types/react": "^16.9.41", "@types/react-autosuggest": "^9.3.14", diff --git a/solr-8.3.1/CHANGES.txt b/solr-8.3.1/CHANGES.txt index 219888b49..78ee2efc1 100644 --- a/solr-8.3.1/CHANGES.txt +++ b/solr-8.3.1/CHANGES.txt @@ -66,7 +66,7 @@ Upgrade Notes ---------------------- * Users who have written test cases that extend SolrTestCaseJ4 may see NullPointerExceptions if - their tests directly reference both SolrTestCaseJ4.initCoreDataDir and SolrTestCaseJ4.deleteCore(). + their tests directly reference both SolrTestCaseJ4.initCoreDataDir and TestCaseJ4.deleteCore(). This change in behavior is due to a bug fix in deleteCore() to ensure the dataDir is properly reset in tests that call initCore()/deleteCore() multiple times in a given test (class). initCoreDataDir is now deprecated, and users are encouraged to use SolrTestCaseJ4.initAndGetDataDir() in it's place. diff --git a/solr-8.3.1/bin/solr b/solr-8.3.1/bin/solr index 596242fac..596242fac 100644..100755 --- a/solr-8.3.1/bin/solr +++ b/solr-8.3.1/bin/solr diff --git a/solr-8.3.1/bin/solr-8983.pid b/solr-8.3.1/bin/solr-8983.pid index 779eb1af5..1f77d281d 100644 --- a/solr-8.3.1/bin/solr-8983.pid +++ b/solr-8.3.1/bin/solr-8983.pid @@ -1 +1 @@ -17656 +1731 diff --git a/solr-8.3.1/server/solr/dash/conf/schema.xml b/solr-8.3.1/server/solr/dash/conf/schema.xml index c0a4bab07..36e803d83 100644 --- a/solr-8.3.1/server/solr/dash/conf/schema.xml +++ b/solr-8.3.1/server/solr/dash/conf/schema.xml @@ -44,6 +44,9 @@ <field name="text" type="text" indexed="true" stored="false" uninvertible="false" multiValued="true"/> <field name="id" type="string" indexed="true" stored="true" uninvertible="false" required="true"/> <field name="_version_" type="plong" indexed="true" stored="true"/> + <field name="proto" type="string" indexed="true" stored="true" uninvertible="false" /> + <field name="_height" type="pdouble" indexed="true" stored="true" uninvertible="false" docValues="true"/> + <dynamicField name="*_t" type="text" indexed="true" stored="true" uninvertible="false" docValues="false"/> <dynamicField name="*_n" type="pdouble" indexed="true" stored="true" uninvertible="false" docValues="false"/> diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 6fa8cf909..2fe3e9778 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -372,6 +372,9 @@ export namespace DocServer { } else if (cached instanceof Promise) { proms.push(cached as any); } + } else if (field) { + proms.push(_cache[field.id] as any); + fieldMap[field.id] = field; } } }); diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx index c3e8d2fff..bc95b5f9a 100644 --- a/src/client/apis/HypothesisAuthenticationManager.tsx +++ b/src/client/apis/HypothesisAuthenticationManager.tsx @@ -18,7 +18,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}> @observable private showPasteTargetState = false; @observable private success: Opt<boolean> = undefined; @observable private displayLauncher = true; - @observable private credentials: string; + @observable private credentials: string = ""; private disposer: Opt<IReactionDisposer>; private set isOpen(value: boolean) { diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 985fcce11..71d6c2ccc 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -13,7 +13,7 @@ export enum DocumentType { INK = "ink", // ink stroke SCREENSHOT = "screenshot", // view of a desktop application FONTICON = "fonticonbox", // font icon - QUERY = "query", // search query + SEARCH = "search", // search query LABEL = "label", // simple text label BUTTON = "button", // onClick button WEBCAM = "webcam", // webcam @@ -31,6 +31,7 @@ export enum DocumentType { COLOR = "color", // color picker (view of a color picker for a color string) YOUTUBE = "youtube", // youtube directory (view of you tube search results) DOCHOLDER = "docholder", // nested document (view of a document) + SEARCHITEM= "searchitem", COMPARISON = "comparison", // before/after view with slider (view of 2 images) GROUP = "group", // group of users diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 959aeac41..7719b3eef 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -21,6 +21,8 @@ import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; import { LinkManager } from "../util/LinkManager"; import { Scripting } from "../util/Scripting"; import { UndoManager } from "../util/UndoManager"; +import { DocumentType } from "./DocumentTypes"; +import { SearchBox } from "../views/search/SearchBox"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; import { ContextMenu } from "../views/ContextMenu"; @@ -39,7 +41,6 @@ import { LabelBox } from "../views/nodes/LabelBox"; import { LinkBox } from "../views/nodes/LinkBox"; import { PDFBox } from "../views/nodes/PDFBox"; import { PresBox } from "../views/nodes/PresBox"; -import { QueryBox } from "../views/nodes/QueryBox"; import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; import { ScriptingBox } from "../views/nodes/ScriptingBox"; import { SliderBox } from "../views/nodes/SliderBox"; @@ -47,7 +48,6 @@ import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; import { PresElementBox } from "../views/presentationview/PresElementBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; -import { DocumentType } from "./DocumentTypes"; import { Networking } from "../Network"; import { Upload } from "../../server/SharedMediaTypes"; const path = require('path'); @@ -197,10 +197,10 @@ export interface DocumentOptions { flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; selectedIndex?: number; syntaxColor?: string; // can be applied to text for syntax highlighting all matches in the text - searchText?: string; //for searchbox - searchQuery?: string; // for queryBox - filterQuery?: string; + searchQuery?: string; // for quersyBox linearViewIsExpanded?: boolean; // is linear view expanded + border?: string; //for searchbox + hovercolor?: string; } class EmptyBox { @@ -230,8 +230,8 @@ export namespace Docs { layout: { view: FormattedTextBox, dataField: "text" }, options: { _height: 150, _xMargin: 10, _yMargin: 10 } }], - [DocumentType.QUERY, { - layout: { view: QueryBox, dataField: defaultDataKey }, + [DocumentType.SEARCH, { + layout: { view: SearchBox, dataField: defaultDataKey }, options: { _width: 400 } }], [DocumentType.COLOR, { @@ -638,8 +638,8 @@ export namespace Docs { return instance; } - export function QueryDocument(options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.QUERY), "", options); + export function SearchDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List<Doc>([]), options); } export function ColorDocument(options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4e59434b5..1f140e145 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,7 +8,7 @@ import { Doc, DocListCast, DocListCastAsync, DataSym } from "../../fields/Doc"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { ScriptField, ComputedField } from "../../fields/ScriptField"; -import { Cast, PromiseValue, StrCast, NumCast } from "../../fields/Types"; +import { Cast, PromiseValue, StrCast, NumCast, BoolCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; import { DragManager } from "./DragManager"; import { Scripting } from "./Scripting"; @@ -45,7 +45,7 @@ export class CurrentUserUtils { if (doc["template-button-query"] === undefined) { const queryTemplate = Docs.Create.MulticolumnDocument( [ - Docs.Create.QueryDocument({ title: "query", _height: 200 }), + Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, lockedPosition: true, title: "query", _height: 200 }), Docs.Create.FreeformDocument([], { title: "data", _height: 100 }) ], { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true } @@ -415,9 +415,6 @@ export class CurrentUserUtils { if (doc.emptyButton === undefined) { doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button" }); } - if (doc.emptySearch === undefined) { - doc.emptySearch = Docs.Create.QueryDocument({ _width: 200, title: "empty search" }); - } if (doc.emptyDocHolder === undefined) { doc.emptyDocHolder = Docs.Create.DocumentDocument( ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any, @@ -512,6 +509,14 @@ export class CurrentUserUtils { ]; } + static setupSearchPanel(doc: Doc) { + if (doc["search-panel"] === undefined) { + doc["search-panel"] = new PrefetchProxy(Docs.Create.SearchDocument({ + _width: 500, _height: 400, backgroundColor: "dimGray", ignoreClick: true, + childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", + })) as any as Doc; + } + } static setupMenuPanel(doc: Doc) { if (doc.menuStack === undefined) { const menuBtns = CurrentUserUtils.menuBtnDescriptions().map(({ title, icon, click }) => @@ -652,7 +657,7 @@ export class CurrentUserUtils { // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static async setupToolsBtnPanel(doc: Doc, sidebarContainer: Doc) { + static async setupToolsBtnPanel(doc: Doc) { // setup a masonry view of all he creators const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); @@ -750,6 +755,8 @@ export class CurrentUserUtils { })) as any as Doc; } } + + static setupUserDoc(doc: Doc) { if (doc["sidebar-userDoc"] === undefined) { doc.treeViewOpen = true; @@ -774,8 +781,8 @@ export class CurrentUserUtils { // setup the list of sidebar mode buttons which determine what is displayed in the sidebar static async setupSidebarButtons(doc: Doc) { - const sidebarContainer = CurrentUserUtils.setupSidebarContainer(doc); - await CurrentUserUtils.setupToolsBtnPanel(doc, sidebarContainer); + CurrentUserUtils.setupSidebarContainer(doc); + await CurrentUserUtils.setupToolsBtnPanel(doc); CurrentUserUtils.setupWorkspaces(doc); CurrentUserUtils.setupCatalog(doc); CurrentUserUtils.setupRecentlyClosed(doc); @@ -813,6 +820,11 @@ export class CurrentUserUtils { // the initial presentation Doc to use static setupDefaultPresentation(doc: Doc) { + if (doc["template-presentation"] === undefined) { + doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ + title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data" + })); + } if (doc.activePresentation === undefined) { doc.activePresentation = Docs.Create.PresDocument(new List<Doc>(), { title: "Presentation", _viewType: CollectionViewType.Stacking, targetDropAction: "alias", @@ -822,9 +834,9 @@ export class CurrentUserUtils { } // Right sidebar is where mobile uploads are contained - static setupRightSidebar(doc: Doc) { + static setupSharingSidebar(doc: Doc) { if (doc["sidebar-sharing"] === undefined) { - doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Mobile Uploads" })); + doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Sharing Sidebar" })); } } @@ -890,14 +902,17 @@ export class CurrentUserUtils { doc.fontFamily = StrCast(doc.fontFamily, "Arial"); doc.fontColor = StrCast(doc.fontColor, "black"); doc.fontHighlight = StrCast(doc.fontHighlight, ""); + doc.defaultColor = StrCast(doc.defaultColor, "white"); + doc.noviceMode = BoolCast(doc.noviceMode, true); doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); 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.setupRightSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing + this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile this.setupMenuPanel(doc); + this.setupSearchPanel(doc); this.setupOverlays(doc); // documents in overlay layer this.setupDockedButtons(doc); // the bottom bar of font icons this.setupDefaultPresentation(doc); // presentation that's initially triggered diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index f1e6155d2..cb0a4bea0 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -134,7 +134,7 @@ export function scriptingGlobal(constructor: { new(...args: any[]): any }) { Scripting.addGlobal(constructor); } -const _scriptingGlobals: { [name: string]: any } = {}; +export const _scriptingGlobals: { [name: string]: any } = {}; let scriptingGlobals: { [name: string]: any } = _scriptingGlobals; const _scriptingDescriptions: { [name: string]: any } = {}; const _scriptingParams: { [name: string]: any } = {}; diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 0a01d8ac7..7b2c601fe 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -29,6 +29,8 @@ export namespace SearchUtil { rows?: number; fq?: string; allowAliases?: boolean; + "facet"?: string; + "facet.field"?: string; } export function Search(query: string, returnDocs: true, options?: SearchParams): Promise<DocSearchResult>; export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>; diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index 6923fe879..41bce8a1b 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -1,7 +1,7 @@ @import "../views/globalCssVariables"; .settings-interface { - background-color: whitesmoke !important; + //background-color: whitesmoke !important; color: grey; width: 450px; height: 300px; @@ -22,85 +22,232 @@ } } -.settings-interface { +.settings-title { + font-size: 25px; + font-weight: bold; + padding-right: 10px; + color: black; +} + +.settings-username { + font-size: 14px; + padding-right: 15px; + color: black; + margin-top: 10px; +} + +.settings-section { display: flex; - flex-direction: column; + border-bottom: 1px solid grey; + padding-bottom: 8px; + padding-top: 6px; - button { - width: 100%; - align-self: center; - background: #252b33; - margin-top: 4px; + .settings-section-title { + font-size: 16; + font-weight: bold; + text-align: left; + color: black; + width: 80; + margin-right: 50px; + } + + &:last-child { + border-bottom: none; + } +} + + +.password-content { + display: flex; + + .password-content-inputs { + width: 100; + + .password-inputs { + border: none; + margin-bottom: 8px; + width: 180; + color: black; + border-radius: 5px; + } + } + + .password-content-buttons { + margin-left: 84px; + width: 100; + + .password-submit { + margin-left: 85px; + } + + .password-forgot { + margin-left: 65px; + margin-top: -20px; + white-space: nowrap; + } + } +} + +.accounts-content { + display: flex; +} + +.modes-content { + display: flex; + + .modes-select { + width: 170px; + margin-right: 65px; + color: black; + border-radius: 5px; &:hover { - background: $main-accent; + cursor: pointer; } } - .delete-button { - background: rgb(227, 86, 86); + .modes-playground { + display: flex; + + .playground-check { + margin-right: 5px; + + &:hover { + cursor: pointer; + } + } + + .playground-text { + color: black; + } } +} - .close-button { - position: absolute; - right: 1em; - top: 1em; +.colorFlyout { + margin-top: 2px; + margin-right: 25px; + + &:hover { cursor: pointer; } - .settings-heading { - letter-spacing: .5em; + .colorFlyout-button { + width: 20px; + height: 20px; + border: 0.5px solid black; + border-radius: 5px; } +} +.preferences-content { + display: flex; + margin-top: 4px; - .settings-body { + .preferences-color { display: flex; - justify-content: space-between; - margin-top: -10; - .settings-type { - display: flex; - flex-direction: column; - flex-basis: 45%; + .preferences-color-text { + color: black; + font-size: 11; + margin-top: 4; + margin-right: 4; + } + } + .preferences-font { + display: flex; + + .preferences-font-text { + color: black; + font-size: 11; + margin-top: 4; + margin-right: 4; } - .settings-content { - padding-left: 1em; - padding-right: 1em; - display: flex; - flex-direction: column; - flex-basis: 70%; - justify-content: space-around; - text-align: left; - - ::placeholder { - color: $intermediate-color; - } + .font-select { + width: 100px; + color: black; + font-size: 9; + margin-right: 6; + border-radius: 5px; - input { - border-radius: 5px; - border: none; - padding: 4px; - min-width: 100%; - margin: 2px 0; + &:hover { + cursor: pointer; } + } - .error-text { - color: #C40233; - } + .size-select { + width: 60px; + color: black; + font-size: 9; + border-radius: 5px; - .success-text { - color: #009F6B; + &:hover { + cursor: pointer; } + } + } +} - p { - padding: 0 0 .1em .2em; - } +.settings-interface { + display: flex; + flex-direction: column; + + button { + width: auto; + align-self: center; + background: #252b33; + margin-right: 15px; + //margin-top: 4px; + + &:hover { + background: $main-accent; } } + // .delete-button { + // background: rgb(227, 86, 86); + // } + + .close-button { + position: absolute; + right: 1em; + top: 1em; + cursor: pointer; + } + + .settings-content { + background: #e4e4e4; + border-radius: 6px; + padding: 10px; + width: 560px; + } + + .settings-top { + display: flex; + margin-bottom: 10px; + } + + + .error-text { + color: #C40233; + width: 300; + margin-left: -20; + font-size: 10; + margin-bottom: 4; + margin-top: -3; + } + + .success-text { + width: 300; + margin-left: -20; + font-size: 10; + margin-bottom: 4; + margin-top: -3; + color: #009F6B; + } + .focus-span { text-decoration: underline; } @@ -138,8 +285,6 @@ color: black; } - - } } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 6276fae96..68ed32c0f 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -1,4 +1,4 @@ -import { observable, runInAction, action } from "mobx"; +import { observable, runInAction, action, computed } from "mobx"; import * as React from "react"; import MainViewModal from "../views/MainViewModal"; import { observer } from "mobx-react"; @@ -15,6 +15,12 @@ import GroupManager from "./GroupManager"; import HypothesisAuthenticationManager from "../apis/HypothesisAuthenticationManager"; import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; import { DocServer } from "../DocServer"; +import { BoolCast, StrCast, NumCast } from "../../fields/Types"; +import { undoBatch } from "./UndoManager"; +import { ColorState, SketchPicker } from "react-color"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; library.add(fa.faTimes); @@ -33,6 +39,9 @@ export default class SettingsManager extends React.Component<{}> { private new_password_ref = React.createRef<HTMLInputElement>(); private new_confirm_ref = React.createRef<HTMLInputElement>(); + + @computed get backgroundColor() { return Doc.UserDoc().defaultColor; } + public open = action(() => { SelectionManager.DeselectAll(); this.isOpen = true; @@ -107,50 +116,151 @@ export default class SettingsManager extends React.Component<{}> { addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "pink !important" }); } + @action + changeMode = (e: any) => { + if (e.currentTarget.value === "Novice") { + Doc.UserDoc().noviceMode = true; + } else { + Doc.UserDoc().noviceMode = false; + } + } + + @action + changeFontFamily = (e: any) => { + Doc.UserDoc().fontFamily = e.currentTarget.value; + } + + @action + changeFontSize = (e: any) => { + Doc.UserDoc().fontSize = e.currentTarget.value; + } + + @action @undoBatch + switchColor = (color: ColorState) => { + const val = String(color.hex); + Doc.UserDoc().defaultColor = val; + return true; + } + private get settingsInterface() { - return ( - <div className={"settings-interface"}> - <div className="settings-heading"> - <h1>settings</h1> - <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} /> - </div> - </div> - <div className="settings-body"> - <div className="settings-type"> - <button onClick={this.onClick} value="password">reset password</button> - <button onClick={this.noviceToggle} value="data">{`Set ${Doc.UserDoc().noviceMode ? "developer" : "novice"} mode`}</button> - <button onClick={this.togglePlaygroundMode}>{`${this.playgroundMode ? "Disable" : "Enable"} playground mode`}</button> - <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button> - <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button> - <button onClick={() => GroupManager.Instance.open()}>Manage groups</button> - <button onClick={() => window.location.assign(Utils.prepend("/logout"))}> - {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"} - </button> + + + const passwordContent = <div className="password-content"> + <div className="password-content-inputs"> + <input className="password-inputs" type="password" placeholder="current password" ref={this.curr_password_ref} /> + <input className="password-inputs" type="password" placeholder="new password" ref={this.new_password_ref} /> + <input className="password-inputs" type="password" placeholder="confirm new password" ref={this.new_confirm_ref} /> + </div> + <div className="password-content-buttons"> + {this.errorText ? <div className="error-text">{this.errorText}</div> : undefined} + {this.successText ? <div className="success-text">{this.successText}</div> : undefined} + <button className="password-submit" onClick={this.dispatchRequest}>submit</button> + <a className="password-forgot" style={{ marginLeft: 65, marginTop: -20 }} + href="/forgotPassword">forgot password?</a> + </div> + </div>; + + const modesContent = <div className="modes-content"> + <select className="modes-select" + onChange={e => this.changeMode(e)}> + <option key={"Novice"} value={"Novice"} selected={BoolCast(Doc.UserDoc().noviceMode)}> + Novice + </option> + <option key={"Developer"} value={"Developer"} selected={!BoolCast(Doc.UserDoc().noviceMode)}> + Developer + </option> + </select> + <div className="modes-playground"> + <input className="playground-check" type="checkbox" + checked={this.playgroundMode} + onChange={undoBatch(action(() => this.togglePlaygroundMode()))} + /><div className="playground-text">Playground Mode</div> + </div> + </div>; + + const accountsContent = <div className="accounts-content"> + <button onClick={this.googleAuthorize} value="data">{`Link to Google`}</button> + <button onClick={this.hypothesisAuthorize} value="data">{`Link to Hypothes.is`}</button> + <button onClick={() => GroupManager.Instance.open()}>Manage groups</button> + </div>; + + const colorBox = <SketchPicker onChange={this.switchColor} + presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', + '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', + '#FFFFFF', '#f1efeb', 'transparent']} + color={StrCast(this.backgroundColor)} />; + + const colorFlyout = <div className="colorFlyout"> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} + content={colorBox}> + <div> + <div className="colorFlyout-button" style={{ backgroundColor: StrCast(this.backgroundColor) }} + onPointerDown={e => e.stopPropagation()} > + <FontAwesomeIcon icon="palette" size="sm" color={StrCast(this.backgroundColor)} /> </div> - {this.settingsContent === "password" ? - <div className="settings-content"> - <input placeholder="current password" ref={this.curr_password_ref} /> - <input placeholder="new password" ref={this.new_password_ref} /> - <input placeholder="confirm new password" ref={this.new_confirm_ref} /> - {this.errorText ? <div className="error-text">{this.errorText}</div> : undefined} - {this.successText ? <div className="success-text">{this.successText}</div> : undefined} - <button onClick={this.dispatchRequest}>submit</button> - <a style={{ marginLeft: 65, marginTop: -20 }} href="/forgotPassword">forgot password?</a> - - </div> - : undefined} - {this.settingsContent === "data" ? - <div className="settings-content"> - <p>WARNING: <br /> - THIS WILL ERASE ALL YOUR CURRENT DOCUMENTS STORED ON DASH. IF YOU WISH TO PROCEED, CLICK THE BUTTON BELOW.</p> - <button className="delete-button">DELETE</button> - </div> - : undefined} </div> + </Flyout> + </div>; + const fontFamilies: string[] = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; + const fontSizes: string[] = ["7pt", "8pt", "9pt", "10pt", "12pt", "14pt", "16pt", "18pt", "20pt", "24pt", "32pt", "48pt", "72pt"]; + + const preferencesContent = <div className="preferences-content"> + <div className="preferences-color"> + <div className="preferences-color-text">Background Color</div> {colorFlyout} </div> - ); + <div className="preferences-font"> + <div className="preferences-font-text">Default Font</div> + <select className="font-select" + onChange={e => this.changeFontFamily(e)}> + {fontFamilies.map((font) => { + return <option key={font} value={font} selected={StrCast(Doc.UserDoc().fontFamily) === font}> + {font} + </option>; + })} + </select> + <select className="size-select" + onChange={e => this.changeFontSize(e)}> + {fontSizes.map((size) => { + return <option key={size} value={size} selected={StrCast(Doc.UserDoc().fontSize) === size}> + {size} + </option>; + })} + </select> + </div> + </div>; + + return (<div className="settings-interface"> + <div className="settings-top"> + <div className="settings-title">Settings</div> + <div className="settings-username">{Doc.CurrentUserEmail}</div> + <button onClick={() => window.location.assign(Utils.prepend("/logout"))} + style={{ right: 35, position: "absolute" }} > + {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"} + </button> + <div className="close-button" onClick={this.close}> + <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} /> + </div> + </div> + <div className="settings-content"> + <div className="settings-section"> + <div className="settings-section-title">Password</div> + <div className="settings-section-context">{passwordContent}</div> + </div> + <div className="settings-section"> + <div className="settings-section-title">Modes</div> + <div className="settings-section-context">{modesContent}</div> + </div> + <div className="settings-section"> + <div className="settings-section-title">Accounts</div> + <div className="settings-section-context">{accountsContent}</div> + </div> + <div className="settings-section" style={{ paddingBottom: 4 }}> + <div className="settings-section-title">Preferences</div> + <div className="settings-section-context">{preferencesContent}</div> + </div> + </div> + </div>); } render() { @@ -160,6 +270,7 @@ export default class SettingsManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} + dialogueBoxStyle={{ width: "600px", height: "340px" }} /> ); } diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 86e0a568a..7467bc043 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -7,7 +7,7 @@ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw; flex-direction: column; background: whitesmoke; - padding-top: 10px; + padding-top: 10px; padding-bottom: 10px; border-radius: 15px; border: solid #BBBBBBBB 1px; @@ -72,6 +72,7 @@ margin-left: 5px; } } + .contextMenu-description { // width: 11vw; //10vw background: whitesmoke; @@ -100,6 +101,8 @@ border-color: $intermediate-color; // rgb(187, 186, 186); border-bottom-style: solid; border-top-style: solid; + + cursor: pointer; } .contextMenu-itemSelected { diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index c2ca93900..09ae14016 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -64,9 +64,13 @@ $linkGap : 3px; text-align: center; border-radius: 50%; pointer-events: auto; - color: $dark-color; - border: $dark-color 1px solid; + background-color: $dark-color; + border: none; transition: 0.2s ease all; + + &:hover { + background-color: $main-accent; + } } .documentButtonBar-linker:hover { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index d45531b76..8748b1880 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -198,7 +198,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const isPinned = targetDoc && Doc.isDocPinned(targetDoc); return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}> <div className="documentButtonBar-linker" - style={{ backgroundColor: isPinned ? "black" : "white", color: isPinned ? "white" : "black" }} + style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white", border: isPinned ? "black 1px solid " : "" }} onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index f9d060681..ec3e754fb 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -6,6 +6,8 @@ import { ObjectField } from '../../fields/ObjectField'; import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import "./EditableView.scss"; import { DragManager } from '../util/DragManager'; +import { ComputedField } from '../../fields/ScriptField'; +import { FieldValue } from '../../fields/Types'; export interface EditableProps { /** @@ -52,6 +54,10 @@ export interface EditableProps { color?: string | undefined; onDrop?: any; placeholder?: string; + highlight?: boolean; + positions?: number[]; + search?: string; + bing?: () => string | undefined; } /** @@ -179,6 +185,34 @@ export class EditableView extends React.Component<EditableProps> { placeholder={this.props.placeholder} />; } + + returnHighlights() { + const results = []; + const contents = this.props.bing!(); + + if (contents !== undefined) { + if (this.props.positions !== undefined) { + const positions = this.props.positions; + const length = this.props.search!.length; + + // contents = String(this.props.contents.valueOf()); + + results.push(<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(0, this.props.positions[0]) : this.props.placeholder?.valueOf()}</span>); + positions.forEach((num, cur) => { + results.push(<span style={{ backgroundColor: "#FFFF00", fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(num, num + length) : this.props.placeholder?.valueOf()}</span>); + let end = 0; + cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; + results.push(<span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{contents ? contents.slice(num + length, end) : this.props.placeholder?.valueOf()}</span>); + } + ); + } + return results; + } + else { + return <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span>; + } + } + render() { if (this._editing && this.props.GetValue() !== undefined) { return this.props.sizeToContent ? @@ -193,11 +227,8 @@ export class EditableView extends React.Component<EditableProps> { ref={this._ref} style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }} onClick={this.onClick} placeholder={this.props.placeholder}> - <span style={{ - fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, - color: this.props.contents ? this.props.color ? this.props.color : "black" : "grey" - }}> - {this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span> + {this.props.highlight === undefined || this.props.positions === undefined || this.props.bing === undefined ? <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize, color: this.props.contents ? "black" : "grey" }}>{this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()}</span> + : this.returnHighlights()} </div> ); } diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index a2a9ceca5..97ed0a901 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -26,7 +26,7 @@ body { height: 100%; border-radius: inherit; position: inherit; - // background: inherit; + // background: inherit; } p { @@ -37,7 +37,7 @@ p { ::-webkit-scrollbar { -webkit-appearance: none; height: 8px; - width: 8px; + width: 8px; } ::-webkit-scrollbar-thumb { @@ -47,7 +47,7 @@ p { // button stuff button { - background: $dark-color; + background: black; outline: none; border: 0px; color: $light-color; diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index a57d22afd..f3fba82bc 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -21,7 +21,7 @@ // add nodes menu. Note that the + button is actually an input label, not an actual button. .mainView-docButtons { position: absolute; - bottom: 20px; + bottom: 35px; left: calc(100% + 5px); z-index: 1; } @@ -103,7 +103,8 @@ } .mainView-propertiesDragger { - background-color: rgb(140, 139, 139); + //background-color: rgb(140, 139, 139); + background-color: lightgrey; height: 55px; width: 17px; position: absolute; @@ -155,8 +156,8 @@ .mainView-menuPanel { width: 60px; - background-color: black; - height: 100%; + background-color: #121721; + height: calc(100% - 32px); //overflow-y: scroll; //overflow-x: hidden; @@ -165,6 +166,7 @@ padding: 7px; padding-left: 7px; width: 100%; + background: black; .mainView-menuPanel-button-wrap { width: 45px; @@ -213,12 +215,11 @@ .mainView-searchPanel { width: 100%; - height: 33px; + height: 32px; background-color: black; color: white; text-align: center; vertical-align: middle; - padding-top: 6px; } .mainView-mainDiv { @@ -297,6 +298,7 @@ position: absolute; z-index: 2; touch-action: none; + background-color: lightgrey; cursor: grab; .mainView-libraryHandle-icon { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7f50dda72..845e93ecb 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -60,6 +60,7 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { undoBatch } from '../util/UndoManager'; +import { SearchBox } from './search/SearchBox'; @observer export class MainView extends React.Component { @@ -79,6 +80,7 @@ export class MainView extends React.Component { @computed private get userDoc() { return Doc.UserDoc(); } @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; } @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } + @computed public get searchDoc() { return Cast(this.userDoc["search-panel"], Doc) as Doc; } @observable public sidebarContent: any = this.userDoc?.sidebar; @observable public panelContent: string = "none"; @@ -191,6 +193,20 @@ export class MainView extends React.Component { if (targets && (targets.length && targets[0].className.toString() !== "timeline-menu-desc" && targets[0].className.toString() !== "timeline-menu-item" && targets[0].className.toString() !== "timeline-menu-input")) { TimelineMenu.Instance.closeMenu(); } + if (targets && targets.length && SearchBox.Instance._searchbarOpen) { + let check = false; + const icon = "icon"; + targets.forEach((thing) => { + if (thing.className.toString() === "collectionSchemaView-table" || (thing as any)?.dataset[icon] === "filter" || thing.className.toString() === "beta" || thing.className.toString() === "collectionSchemaView-menuOptions-wrapper") { + check = true; + } + }); + if (check === false) { + SearchBox.Instance.closeSearch(); + } + } + + }); globalPointerUp = () => this.isPointerDown = false; @@ -316,6 +332,16 @@ export class MainView extends React.Component { defaultBackgroundColors = (doc: Opt<Doc>) => { if (this.panelContent === doc?.title) return "lightgrey"; + + if (doc?.type === DocumentType.COL) { + if (doc.title === "Basic Item Creators" || doc.title === "sidebar-tools" + || doc.title === "sidebar-recentlyClosed" || doc.title === "sidebar-catalog" + || doc.title === "Mobile Uploads" || doc.title === "COLLECTION_PROTO" + || doc.title === "Advanced Item Prototypes" || doc.title === "all Creators") { + return "lightgrey"; + } + return StrCast(Doc.UserDoc().defaultColor); + } if (this.darkScheme) { switch (doc?.type) { case DocumentType.FONTICON: return "white"; @@ -340,6 +366,7 @@ export class MainView extends React.Component { } } } + @computed get mainDocView() { return <DocumentView Document={this.mainContainer!} @@ -368,11 +395,12 @@ export class MainView extends React.Component { renderDepth={-1} />; } + @computed get dockingContent() { TraceMobx(); const mainContainer = this.mainContainer; const width = this.flyoutWidth + this.propertiesWidth(); - return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)` }}> + return <div className="mainContent-div" onDrop={this.onDrop} style={{ width: `calc(100% - ${width}px)`, height: `calc(100% - 32px)` }}> {!mainContainer ? (null) : this.mainDocView} </div>; } @@ -417,11 +445,11 @@ export class MainView extends React.Component { @computed get flyout() { if (!this.sidebarContent) return null; return <div className="mainView-libraryFlyout"> - <div className="mainView-contentArea" style={{ position: "relative", height: `100%`, width: "100%", overflow: "visible" }}> - {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close" + <div className="mainView-contentArea" style={{ position: "relative", height: `calc(100% - 32px)`, width: "100%", overflow: "visible" }}> + {/* {this.flyoutWidth > 0 ? <div className="mainView-libraryFlyout-close" onPointerDown={this.closeFlyout}> <FontAwesomeIcon icon="times" color="black" size="lg" /> - </div> : null} + </div> : null} */} <DocumentView Document={this.sidebarContent} @@ -449,6 +477,7 @@ export class MainView extends React.Component { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} relative={true} + forcedBackgroundColor={() => "lightgrey"} /> </div> {this.docButtons}</div>; @@ -488,7 +517,7 @@ export class MainView extends React.Component { } - @action @undoBatch + @action closeFlyout = () => { this._lastButton && (this._lastButton.color = "white"); this._lastButton && (this._lastButton._backgroundColor = ""); @@ -499,7 +528,7 @@ export class MainView extends React.Component { get groupManager() { return GroupManager.Instance; } _lastButton: Doc | undefined; - @action @undoBatch + @action selectMenu = (button: Doc, str: string) => { this._lastButton && (this._lastButton.color = "white"); this._lastButton && (this._lastButton._backgroundColor = ""); @@ -528,7 +557,7 @@ export class MainView extends React.Component { return true; } - @action @undoBatch + @action closeProperties = () => { CurrentUserUtils.propertiesWidth = 0; } @@ -556,7 +585,8 @@ export class MainView extends React.Component { <div className="mainView-flyoutContainer" style={{ width: this.flyoutWidth }}> {this.flyoutWidth !== 0 ? <div className="mainView-libraryHandle" onPointerDown={this.onFlyoutPointerDown} - style={{ backgroundColor: 'lightgrey' }}> + //style={{ backgroundColor: '#8c8b8b' }} + > <span title="library View Dragger" style={{ width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw", //height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh", @@ -583,7 +613,7 @@ export class MainView extends React.Component { <div className="mainView-propertiesDragger" title="Properties View Dragger" onPointerDown={this.onPropertiesPointerDown} style={{ right: rightFlyout, top: "50%" }}> <div className="mainView-propertiesDragger-icon"> - <FontAwesomeIcon icon={this.propertiesIcon} color="white" size="sm" /> </div> + <FontAwesomeIcon icon={this.propertiesIcon} color="black" size="sm" /> </div> </div> } {this.propertiesWidth() < 10 ? (null) : @@ -593,7 +623,6 @@ export class MainView extends React.Component { } @computed get mainContent() { - //const n = (RichTextMenu.Instance?.Pinned ? 1 : 0) + (CollectionMenu.Instance?.Pinned ? 1 : 0); const n = (CollectionMenu.Instance?.Pinned ? 1 : 0); const height = `calc(100% - ${n * Number(ANTIMODEMENU_HEIGHT.replace("px", ""))}px)`; const pinned = FormatShapePane.Instance?.Pinned; @@ -716,8 +745,32 @@ export class MainView extends React.Component { @computed get search() { return <div className="mainView-searchPanel"> - <div style={{ float: "left", marginLeft: "10px" }}>{Doc.CurrentUserEmail}</div> - <div>SEARCH GOES HERE</div> + {/* <div style={{ float: "left", marginLeft: "10px" }}>{Doc.CurrentUserEmail}</div> */} + <div><DocumentView Document={this.searchDoc} + DataDoc={undefined} + LibraryPath={emptyPath} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + rootSelected={returnTrue} + onClick={undefined} + backgroundColor={this.defaultBackgroundColors} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + ContentScaling={returnOne} + NativeHeight={returnZero} + NativeWidth={returnZero} + PanelWidth={this.getPWidth} + PanelHeight={this.getPHeight} + renderDepth={0} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + docFilters={returnEmptyFilter} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + /></div> </div>; } @@ -732,7 +785,7 @@ export class MainView extends React.Component { <GoogleAuthenticationManager /> <HypothesisAuthenticationManager /> <DocumentDecorations /> - {/* {this.search} */} + {this.search} <CollectionMenu /> <FormatShapePane /> <div style={{ display: "none" }}><RichTextMenu key="rich" /></div> diff --git a/src/client/views/MainViewModal.tsx b/src/client/views/MainViewModal.tsx index 66ea2dbf8..19387f619 100644 --- a/src/client/views/MainViewModal.tsx +++ b/src/client/views/MainViewModal.tsx @@ -21,7 +21,9 @@ export default class MainViewModal extends React.Component<MainViewOverlayProps> const dialogueOpacity = p.dialogueBoxDisplayedOpacity || 1; const overlayOpacity = p.overlayDisplayedOpacity || 0.4; return !p.isDisplayed ? (null) : ( - <div style={{ pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" }}> + <div style={{ + pointerEvents: p.isDisplayed && p.interactive ? "all" : "none" + }}> <div className={"dialogue-box"} style={{ diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss index 1cba252de..6199d34d0 100644 --- a/src/client/views/PropertiesButtons.scss +++ b/src/client/views/PropertiesButtons.scss @@ -21,9 +21,8 @@ $linkGap : 3px; .propertiesButtons-linkButton-empty, .propertiesButtons-linkButton-nonempty { height: 30px; - width: 30px; - border-radius: 5px; - opacity: 0.9; + width: 32px; + border-radius: 6px; pointer-events: auto; background-color: #121721; color: #fcfbf7; @@ -36,6 +35,7 @@ $linkGap : 3px; justify-content: center; align-items: center; margin-right: 10px; + margin-left: 3.5px; &:hover { background: $main-accent; @@ -68,23 +68,35 @@ $linkGap : 3px; padding-right: 5px; width: 25px; border-radius: 5px; - margin-right: 18px; + margin-right: 22px; margin-bottom: 8px; } +.propertiesButtons-title { + background: #121721; + color: white; + font-size: 6px; + width: 40px; + padding: 3px; + height: 13px; + border-radius: 7px; + text-transform: uppercase; + text-align: center; + margin-top: -4px; +} + .propertiesButtons-linker { height: 30px; - width: 30px; + width: 32px; text-align: center; - border-radius: 5px; + border-radius: 6px; pointer-events: auto; - // color: $dark-color; - // border: $dark-color 1px solid; - background-color: #252b33; + background-color: #121721; color: #fcfbf7; transition: 0.2s ease all; margin-right: 5px; padding-top: 5px; + margin-left: 3.5px; &:hover { background: $main-accent; diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 7d8a75dda..29afd447f 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -28,7 +28,6 @@ import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils'; import { ImageField } from '../../fields/URLField'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { DocumentType } from '../documents/DocumentTypes'; -import { CollectionFreeFormView } from './collections/collectionFreeForm/CollectionFreeFormView'; import { InkField } from '../../fields/InkField'; import { PresBox } from './nodes/PresBox'; const higflyout = require("@hig/flyout"); @@ -129,18 +128,22 @@ export class PropertiesButtons extends React.Component<{}, {}> { const targetDoc = this.selectedDoc; const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined; const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none"; - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div></>}> - <div - className="propertiesButtons-linker" - style={{ animation }} - onClick={async () => { - await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); - !published && runInAction(() => this.isAnimatingPulse = true); - PropertiesButtons.hasPushedHack = false; - targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; - }}> - <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "sm" : "xs"} /> - </div></Tooltip>; + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{`${published ? "Push" : "Publish"} to Google Docs`}</div>} placement="top"> + <div> + <div + className="propertiesButtons-linker" + style={{ animation }} + onClick={async () => { + await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); + !published && runInAction(() => this.isAnimatingPulse = true); + PropertiesButtons.hasPushedHack = false; + targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; + }}> + <FontAwesomeIcon className="documentdecorations-icon" icon={published ? (this.pushIcon as any) : cloud} size={published ? "lg" : "sm"} /> + </div> + <div className="propertiesButtons-title">Google</div> + </div> + </Tooltip>; } @computed @@ -159,62 +162,76 @@ export class PropertiesButtons extends React.Component<{}, {}> { })(); return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) : <Tooltip - title={<><div className="dash-tooltip">{title}</div></>}> - <div className="propertiesButtons-linker" - style={{ backgroundColor: this.pullColor }} - onPointerEnter={action(e => { - if (e.altKey) { - this.openHover = UtilityButtonState.OpenExternally; - } else if (e.shiftKey) { - this.openHover = UtilityButtonState.OpenRight; - } - })} - onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)} - onClick={async e => { - const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; - if (e.shiftKey) { - e.preventDefault(); - let googleDoc = await Cast(dataDoc.googleDoc, Doc); - if (!googleDoc) { - const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false }; - googleDoc = Docs.Create.WebDocument(googleDocUrl, options); - dataDoc.googleDoc = googleDoc; + title={<><div className="dash-tooltip">{title}</div></>} placement="top"> + <div> + <div className="propertiesButtons-linker" + style={{ backgroundColor: this.pullColor }} + onPointerEnter={action(e => { + if (e.altKey) { + this.openHover = UtilityButtonState.OpenExternally; + } else if (e.shiftKey) { + this.openHover = UtilityButtonState.OpenRight; } - CollectionDockingView.AddRightSplit(googleDoc); - } else if (e.altKey) { - e.preventDefault(); - window.open(googleDocUrl); - } else { - this.clearPullColor(); - PropertiesButtons.hasPulledHack = false; - targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; - dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); - } - }}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" - style={{ WebkitAnimation: animation, MozAnimation: animation }} - icon={(() => { - switch (this.openHover) { - default: - case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; - case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; - case UtilityButtonState.OpenExternally: return "share"; + })} + onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)} + onClick={async e => { + const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; + if (e.shiftKey) { + e.preventDefault(); + let googleDoc = await Cast(dataDoc.googleDoc, Doc); + if (!googleDoc) { + const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, isAnnotating: false, UseCors: false }; + googleDoc = Docs.Create.WebDocument(googleDocUrl, options); + dataDoc.googleDoc = googleDoc; + } + CollectionDockingView.AddRightSplit(googleDoc); + } else if (e.altKey) { + e.preventDefault(); + window.open(googleDocUrl); + } else { + this.clearPullColor(); + PropertiesButtons.hasPulledHack = false; + targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; + dataDoc.unchanged && runInAction(() => this.isAnimatingFetch = true); } - })()} - /> - </div></Tooltip>; + }}> + <FontAwesomeIcon className="documentdecorations-icon" size="lg" + color="black" + style={{ WebkitAnimation: animation, MozAnimation: animation }} + icon={(() => { + switch (this.openHover) { + default: + case UtilityButtonState.Default: return dataDoc.unchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; + case UtilityButtonState.OpenExternally: return "share"; + } + })()} + /> + </div> + <div className="propertiesButtons-title" style={{ backgroundColor: "white", color: "black" }}>Fetch</div> + </div> + </Tooltip>; } @computed get pinButton() { const targetDoc = this.selectedDoc; const isPinned = targetDoc && Doc.isDocPinned(targetDoc); - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}> - <div className="propertiesButtons-linker" - style={{ backgroundColor: isPinned ? "" : "white", color: isPinned ? "white" : "black" }} - onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" - /> - </div></Tooltip>; + return !targetDoc ? (null) : <Tooltip title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top"> + <div> + <div className="propertiesButtons-linker" + style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white" }} + onClick={e => DockedFrameRenderer.PinDoc(targetDoc, isPinned)}> + <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" + /> + </div> + + <div className="propertiesButtons-title" style={{ + backgroundColor: Doc.isDocPinned(targetDoc) ? "white" : "black", + color: Doc.isDocPinned(targetDoc) ? "black" : "white" + }} + >{Doc.isDocPinned(targetDoc) ? "Unpin" : "Pin"}</div> + </div> + </Tooltip>; } @computed @@ -252,12 +269,15 @@ export class PropertiesButtons extends React.Component<{}, {}> { get metadataButton() { //const view0 = this.view0; if (this.selectedDoc) { - return <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>}> + return <Tooltip title={<><div className="dash-tooltip">Show metadata panel</div></>} placement="top"> <div className="propertiesButtons-linkFlyout"> <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={<MetadataEntryMenu docs={[this.selectedDoc]} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}> - <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} > - {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" />} + <div> + <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} > + {<FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="lg" />} + </div> + <div className="propertiesButtons-title">Metadata</div> </div> </Flyout> </div></Tooltip>; @@ -296,12 +316,15 @@ export class PropertiesButtons extends React.Component<{}, {}> { Array.from(Object.values(Templates.TemplateList)).map(template => templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean))); return !docView ? (null) : - <Tooltip title={<><div className="dash-tooltip">Customize layout</div></>}> + <Tooltip title={<><div className="dash-tooltip">Customize layout</div></>} placement="top"> <div className="propertiesButtons-linkFlyout"> <Flyout anchorPoint={anchorPoints.LEFT_TOP} //onOpen={action(() => this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} content={<TemplateMenu docViews={views.filter(v => v).map(v => v as DocumentView)} templates={templates} />}> - <div className={"propertiesButtons-linkButton-empty"} > - {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="sm" />} + <div> + <div className={"propertiesButtons-linkButton-empty"} > + {<FontAwesomeIcon className="documentdecorations-icon" icon="edit" size="lg" />} + </div> + <div className="propertiesButtons-title">Layout</div> </div> </Flyout> </div></Tooltip>; @@ -324,12 +347,15 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get copyButton() { const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Tap or Drag to create an alias"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - ref={this._dragRef} - onPointerDown={this.onAliasButtonDown} - onClick={this.onCopy}> - {<FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="sm" />} + return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Tap or Drag to create an alias"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + ref={this._dragRef} + onPointerDown={this.onAliasButtonDown} + onClick={this.onCopy}> + {<FontAwesomeIcon className="documentdecorations-icon" icon="copy" size="lg" />} + </div> + <div className="propertiesButtons-title">Alias</div> </div> </Tooltip>; } @@ -344,11 +370,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{this.selectedDoc?.lockedPosition ? - "Unlock Position" : "Lock Position"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={this.onLock} > - {<FontAwesomeIcon className="documentdecorations-icon" - icon={BoolCast(this.selectedDoc?.lockedPosition) ? "unlock" : "lock"} size="sm" />} + "Unlock Position" : "Lock Position"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + style={{ backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "" }} + onPointerDown={this.onLock} > + {<FontAwesomeIcon className="documentdecorations-icon" + color={BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white"} + icon={BoolCast(this.selectedDoc?.lockedPosition) ? "unlock" : "lock"} size="lg" />} + </div> + <div className="propertiesButtons-title" style={{ + backgroundColor: BoolCast(this.selectedDoc?.lockedPosition) ? "white" : "black", + color: BoolCast(this.selectedDoc?.lockedPosition) ? "black" : "white" + }}>Position </div> </div> </Tooltip>; } @@ -357,15 +391,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { get downloadButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Download Document"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={async () => { - if (this.selectedDocumentView?.props.Document) { - Doc.Zip(this.selectedDocumentView?.props.Document); - } - }}> - {<FontAwesomeIcon className="propertiesButtons-icon" - icon="download" size="sm" />} + title={<><div className="dash-tooltip">{"Download Document"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={async () => { + if (this.selectedDocumentView?.props.Document) { + Doc.Zip(this.selectedDocumentView?.props.Document); + } + }}> + {<FontAwesomeIcon className="propertiesButtons-icon" + icon="download" size="lg" />} + </div> + <div className="propertiesButtons-title"> downld </div> </div> </Tooltip>; } @@ -374,11 +411,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { get deleteButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Delete Document"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={this.deleteDocument}> - {<FontAwesomeIcon className="propertiesButtons-icon" - icon="trash-alt" size="sm" />} + title={<><div className="dash-tooltip">{"Delete Document"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={this.deleteDocument}> + {<FontAwesomeIcon className="propertiesButtons-icon" + icon="trash-alt" size="lg" />} + </div> + <div className="propertiesButtons-title"> delete </div> </div> </Tooltip>; } @@ -393,15 +433,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { get sharingButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Share Document"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={() => { - if (this.selectedDocumentView) { - SharingManager.Instance.open(this.selectedDocumentView); - } - }}> - {<FontAwesomeIcon className="propertiesButtons-icon" - icon="users" size="sm" />} + title={<><div className="dash-tooltip">{"Share Document"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={() => { + if (this.selectedDocumentView) { + SharingManager.Instance.open(this.selectedDocumentView); + } + }}> + {<FontAwesomeIcon className="propertiesButtons-icon" + icon="users" size="lg" />} + </div> + <div className="propertiesButtons-title"> share </div> </div> </Tooltip>; } @@ -409,15 +452,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get onClickButton() { if (this.selectedDoc) { - return <Tooltip title={<><div className="dash-tooltip">Choose onClick behavior</div></>}> - <div className="propertiesButtons-linkFlyout"> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} - content={this.onClickFlyout}> - <div className={"propertiesButtons-linkButton-" + "empty"} onPointerDown={e => e.stopPropagation()} > - {<FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="sm" />} - </div> - </Flyout> - </div></Tooltip>; + return <Tooltip title={<><div className="dash-tooltip">Choose onClick behavior</div></>} placement="top"> + <div> + <div className="propertiesButtons-linkFlyout"> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} + content={this.onClickFlyout}> + <div className={"propertiesButtons-linkButton-empty"} onPointerDown={e => e.stopPropagation()} > + {<FontAwesomeIcon className="documentdecorations-icon" icon="mouse-pointer" size="lg" />} + </div> + </Flyout> + </div> + <div className="propertiesButtons-title"> onclick </div> + </div> + </Tooltip>; } else { return null; } @@ -504,15 +551,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { get googlePhotosButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{"Export to Google Photos"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={() => { - if (this.selectedDocumentView) { - GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDocumentView.Document }).then(console.log); - } - }}> - {<FontAwesomeIcon className="documentdecorations-icon" - icon="cloud-upload-alt" size="sm" />} + title={<><div className="dash-tooltip">{"Export to Google Photos"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={() => { + if (this.selectedDocumentView) { + GooglePhotos.Export.CollectionToAlbum({ collection: this.selectedDocumentView.Document }).then(console.log); + } + }}> + {<FontAwesomeIcon className="documentdecorations-icon" + icon="cloud-upload-alt" size="lg" />} + </div> + <div className="propertiesButtons-title"> google </div> </div> </Tooltip>; } @@ -521,13 +571,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { get clustersButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{this.selectedDoc?.useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - style={{ backgroundColor: this.selectedDoc?.useClusters ? "#a0a0a0" : "" }} - onPointerDown={this.changeClusters}> - {<FontAwesomeIcon className="documentdecorations-icon" - color={this.selectedDoc?.useClusters ? "black" : "white"} - icon="braille" size="sm" />} + title={<><div className="dash-tooltip">{this.selectedDoc?.useClusters ? "Stop Showing Clusters" : "Show Clusters"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + style={{ backgroundColor: this.selectedDoc?.useClusters ? "white" : "" }} + onPointerDown={this.changeClusters}> + {<FontAwesomeIcon className="documentdecorations-icon" + color={this.selectedDoc?.useClusters ? "black" : "white"} + icon="braille" size="lg" />} + </div> + <div className="propertiesButtons-title" style={{ + backgroundColor: this.selectedDoc?.useClusters ? "white" : "black", + color: this.selectedDoc?.useClusters ? "black" : "white" + }}> clusters </div> </div> </Tooltip>; } @@ -546,13 +602,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { get fitContentButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - style={{ backgroundColor: this.selectedDoc?._fitToBox ? "#a0a0a0" : "" }} - onPointerDown={this.changeFitToBox}> - {<FontAwesomeIcon className="documentdecorations-icon" - color={this.selectedDoc?._fitToBox ? "black" : "white"} - icon="expand" size="sm" />} + title={<><div className="dash-tooltip">{this.selectedDoc?._fitToBox ? "Stop Fitting Content" : "Fit Content"}</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + style={{ backgroundColor: this.selectedDoc?._fitToBox ? "white" : "" }} + onPointerDown={this.changeFitToBox}> + {<FontAwesomeIcon className="documentdecorations-icon" + color={this.selectedDoc?._fitToBox ? "black" : "white"} + icon="expand" size="lg" />} + </div> + <div className="propertiesButtons-title" style={{ + backgroundColor: this.selectedDoc?._fitToBox ? "white" : "black", + color: this.selectedDoc?._fitToBox ? "black" : "white" + }}> {this.selectedDoc?._fitToBox ? "unfit" : "fit"} </div> </div> </Tooltip>; } @@ -573,11 +635,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { get maskButton() { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : <Tooltip - title={<><div className="dash-tooltip">Make Mask</div></>}> - <div className={"propertiesButtons-linkButton-empty"} - onPointerDown={this.makeMask}> - {<FontAwesomeIcon className="documentdecorations-icon" - color="white" icon="paint-brush" size="sm" />} + title={<><div className="dash-tooltip">Make Mask</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"} + onPointerDown={this.makeMask}> + {<FontAwesomeIcon className="documentdecorations-icon" + color="white" icon="paint-brush" size="lg" />} + </div> + <div className="propertiesButtons-title"> mask </div> </div> </Tooltip>; } @@ -585,13 +650,16 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get contextButton() { if (this.selectedDoc) { - return <Tooltip title={<><div className="dash-tooltip">Show Context</div></>}> - <div className={"propertiesButtons-linkButton-empty"}> - <ParentDocSelector Document={this.selectedDoc} addDocTab={(doc, where) => { - where === "onRight" ? CollectionDockingView.AddRightSplit(doc) : - this.selectedDocumentView?.props.addDocTab(doc, "onRight"); - return true; - }} /> + return <Tooltip title={<><div className="dash-tooltip">Show Context</div></>} placement="top"> + <div> + <div className={"propertiesButtons-linkButton-empty"}> + <ParentDocSelector Document={this.selectedDoc} addDocTab={(doc, where) => { + where === "onRight" ? CollectionDockingView.AddRightSplit(doc) : + this.selectedDocumentView?.props.addDocTab(doc, "onRight"); + return true; + }} /> + </div> + <div className="propertiesButtons-title"> context </div> </div> </Tooltip>; } else { @@ -657,9 +725,6 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button"> {this.onClickButton} </div> - {/* <div className="propertiesButtons-button"> - {this.contextButton} - </div> */} <div className="propertiesButtons-button"> {this.sharingButton} </div> @@ -687,6 +752,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className="propertiesButtons-button" style={{ display: !isInk ? "none" : "" }}> {this.maskButton} </div> + <div className="propertiesButtons-button"> + {this.contextButton} + </div> </div> </div>; } diff --git a/src/client/views/SearchDocBox.tsx b/src/client/views/SearchDocBox.tsx deleted file mode 100644 index 084f952a3..000000000 --- a/src/client/views/SearchDocBox.tsx +++ /dev/null @@ -1,428 +0,0 @@ -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faBullseye, faLink } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -//import "./SearchBoxDoc.scss"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; -import { returnFalse, returnZero } from "../../Utils"; -import { Docs } from "../documents/Documents"; -import { SearchUtil } from "../util/SearchUtil"; -import { EditableView } from "./EditableView"; -import { ContentFittingDocumentView } from "./nodes/ContentFittingDocumentView"; -import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { FilterBox } from "./search/FilterBox"; -import { SearchItem } from "./search/SearchItem"; -import React = require("react"); - -export interface RecProps { - documents: { preview: Doc, similarity: number }[]; - node: Doc; - -} - -library.add(faBullseye, faLink); -export const keyPlaceholder = "Query"; - -@observer -export class SearchDocBox extends React.Component<FieldViewProps> { - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchDocBox, fieldKey); } - - // @observable private _display: boolean = false; - @observable private _pageX: number = 0; - @observable private _pageY: number = 0; - @observable private _width: number = 0; - @observable private _height: number = 0; - @observable.shallow private _docViews: JSX.Element[] = []; - // @observable private _documents: { preview: Doc, score: number }[] = []; - private previewDocs: Doc[] = []; - - constructor(props: FieldViewProps) { - super(props); - this.editingMetadata = this.editingMetadata || false; - //SearchBox.Instance = this; - this.resultsScrolled = this.resultsScrolled.bind(this); - } - - - @computed - private get editingMetadata() { - return BoolCast(this.props.Document.editingMetadata); - } - - private set editingMetadata(value: boolean) { - this.props.Document.editingMetadata = value; - } - - static readonly buffer = 20; - - componentDidMount() { - runInAction(() => { - this.query = StrCast(this.props.Document.searchText); - this.content = (Docs.Create.TreeDocument(DocListCast(Doc.GetProto(this.props.Document).data), { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query })); - - }); - if (this.inputRef.current) { - this.inputRef.current.focus(); - runInAction(() => { - this._searchbarOpen = true; - }); - } - } - - @observable - private content: Doc | undefined; - - @action - updateKey = async (newKey: string) => { - this.query = newKey; - if (newKey.length > 1) { - const newdocs = await this.getAllResults(this.query); - const things = newdocs.docs; - runInAction(() => { - this.content = Docs.Create.TreeDocument(things, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs:` + this.query }); - }); - } - - - //this.keyRef.current && this.keyRef.current.setIsFocused(false); - //this.query.length === 0 && (this.query = keyPlaceholder); - return true; - } - - @computed - public get query() { - return StrCast(this.props.Document.query); - } - - public set query(value: string) { - this.props.Document.query = value; - } - - @observable private _searchString: string = ""; - @observable private _resultsOpen: boolean = false; - @observable private _searchbarOpen: boolean = false; - @observable private _results: [Doc, string[], string[]][] = []; - private _resultsSet = new Map<Doc, number>(); - @observable private _openNoResults: boolean = false; - @observable private _visibleElements: JSX.Element[] = []; - - private resultsRef = React.createRef<HTMLDivElement>(); - public inputRef = React.createRef<HTMLInputElement>(); - - private _isSearch: ("search" | "placeholder" | undefined)[] = []; - private _numTotalResults = -1; - private _endIndex = -1; - - - private _maxSearchIndex: number = 0; - private _curRequest?: Promise<any> = undefined; - - @action - getViews = async (doc: Doc) => { - const results = await SearchUtil.GetViewsOfDocument(doc); - let toReturn: Doc[] = []; - await runInAction(() => { - toReturn = results; - }); - return toReturn; - } - - @action.bound - onChange(e: React.ChangeEvent<HTMLInputElement>) { - this._searchString = e.target.value; - - this._openNoResults = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - this._maxSearchIndex = 0; - } - - enter = async (e: React.KeyboardEvent) => { - if (e.key === "Enter") { - const newdocs = await this.getAllResults(this.query); - this.content = Docs.Create.TreeDocument(newdocs.docs, { _width: 200, _height: 400, _chromeStatus: "disabled", title: `Search Docs: "Results"` }); - } - } - - - @action - submitSearch = async () => { - let query = this._searchString; - query = FilterBox.Instance.getFinalQuery(query); - this._results = []; - this._resultsSet.clear(); - this._isSearch = []; - this._visibleElements = []; - FilterBox.Instance.closeFilter(); - - //if there is no query there should be no result - if (query === "") { - return; - } - else { - this._endIndex = 12; - this._maxSearchIndex = 0; - this._numTotalResults = -1; - await this.getResults(query); - } - - runInAction(() => { - this._resultsOpen = true; - this._searchbarOpen = true; - this._openNoResults = true; - this.resultsScrolled(); - }); - } - - getAllResults = async (query: string) => { - return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); - } - - private get filterQuery() { - const types = FilterBox.Instance.filterTypes; - const includeDeleted = FilterBox.Instance.getDataStatus(); - return "NOT baseProto_b:true" + (includeDeleted ? "" : " AND NOT deleted_b:true") + (types ? ` AND (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}" OR type_t:"extension"`).join(" ")})` : ""); - } - - - private NumResults = 25; - private lockPromise?: Promise<void>; - getResults = async (query: string) => { - if (this.lockPromise) { - await this.lockPromise; - } - this.lockPromise = new Promise(async res => { - while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { - this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { - - // happens at the beginning - if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { - this._numTotalResults = res.numFound; - } - - const highlighting = res.highlighting || {}; - const highlightList = res.docs.map(doc => highlighting[doc[Id]]); - const lines = new Map<string, string[]>(); - res.docs.map((doc, i) => lines.set(doc[Id], res.lines[i])); - const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc)); - const highlights: typeof res.highlighting = {}; - docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); - const filteredDocs = FilterBox.Instance.filterDocsByType(docs); - runInAction(() => { - // this._results.push(...filteredDocs); - filteredDocs.forEach(doc => { - const index = this._resultsSet.get(doc); - const highlight = highlights[doc[Id]]; - const line = lines.get(doc[Id]) || []; - const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; - if (index === undefined) { - this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights, line]); - } else { - this._results[index][1].push(...hlights); - this._results[index][2].push(...line); - } - }); - }); - - this._curRequest = undefined; - })); - this._maxSearchIndex += this.NumResults; - - await this._curRequest; - } - this.resultsScrolled(); - res(); - }); - return this.lockPromise; - } - - collectionRef = React.createRef<HTMLSpanElement>(); - startDragCollection = async () => { - const res = await this.getAllResults(FilterBox.Instance.getFinalQuery(this._searchString)); - const filtered = FilterBox.Instance.filterDocsByType(res.docs); - const docs = filtered.map(doc => { - const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); - if (isProto) { - return Doc.MakeDelegate(doc); - } else { - return Doc.MakeAlias(doc); - } - }); - let x = 0; - let y = 0; - for (const doc of docs.map(d => Doc.Layout(d))) { - doc.x = x; - doc.y = y; - const size = 200; - const aspect = NumCast(doc._nativeHeight) / NumCast(doc._nativeWidth, 1); - if (aspect > 1) { - doc._height = size; - doc._width = size / aspect; - } else if (aspect > 0) { - doc._width = size; - doc._height = size * aspect; - } else { - doc._width = size; - doc._height = size; - } - x += 250; - if (x > 1000) { - x = 0; - y += 300; - } - } - //return Docs.Create.TreeDocument(docs, { _width: 200, _height: 400, backgroundColor: "grey", title: `Search Docs: "${this._searchString}"` }); - return Docs.Create.QueryDocument({ _width: 200, _height: 400, searchText: this._searchString, title: `Query Docs: "${this._searchString}"` }); - } - - @action.bound - openSearch(e: React.SyntheticEvent) { - e.stopPropagation(); - this._openNoResults = false; - FilterBox.Instance.closeFilter(); - this._resultsOpen = true; - this._searchbarOpen = true; - FilterBox.Instance._pointerTime = e.timeStamp; - } - - @action.bound - closeSearch = () => { - FilterBox.Instance.closeFilter(); - this.closeResults(); - this._searchbarOpen = false; - } - - @action.bound - closeResults() { - this._resultsOpen = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - } - - @action - resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { - if (!this.resultsRef.current) return; - const scrollY = e ? e.currentTarget.scrollTop : this.resultsRef.current ? this.resultsRef.current.scrollTop : 0; - const itemHght = 53; - const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); - const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this.resultsRef.current.getBoundingClientRect().height / itemHght))); - - this._endIndex = endIndex === -1 ? 12 : endIndex; - - if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { - this._visibleElements = [<div className="no-result">No Search Results</div>]; - return; - } - - if (this._numTotalResults <= this._maxSearchIndex) { - this._numTotalResults = this._results.length; - } - - // only hit right at the beginning - // visibleElements is all of the elements (even the ones you can't see) - else if (this._visibleElements.length !== this._numTotalResults) { - // undefined until a searchitem is put in there - this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - // indicates if things are placeholders - this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - } - - for (let i = 0; i < this._numTotalResults; i++) { - //if the index is out of the window then put a placeholder in - //should ones that have already been found get set to placeholders? - if (i < startIndex || i > endIndex) { - if (this._isSearch[i] !== "placeholder") { - this._isSearch[i] = "placeholder"; - this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; - } - } - else { - if (this._isSearch[i] !== "search") { - let result: [Doc, string[], string[]] | undefined = undefined; - if (i >= this._results.length) { - this.getResults(this._searchString); - if (i < this._results.length) result = this._results[i]; - if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; - } - } - else { - result = this._results[i]; - if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; - } - } - } - } - } - if (this._maxSearchIndex >= this._numTotalResults) { - this._visibleElements.length = this._results.length; - this._isSearch.length = this._results.length; - } - } - - @computed - get resFull() { return this._numTotalResults <= 8; } - - @computed - get resultHeight() { return this._numTotalResults * 70; } - - render() { - const isEditing = this.editingMetadata; - return !this.content ? (null) : ( - <div style={{ pointerEvents: "all" }}> - <ContentFittingDocumentView {...this.props} - Document={this.content} - rootSelected={returnFalse} - bringToFront={returnFalse} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - NativeWidth={returnZero} - NativeHeight={returnZero} - parentActive={this.props.active} - ScreenToLocalTransform={this.props.ScreenToLocalTransform}> - </ContentFittingDocumentView> - <div - style={{ - position: "absolute", - right: 0, - width: 20, - height: 20, - background: "black", - pointerEvents: "all", - opacity: 1, - transition: "0.4s opacity ease", - zIndex: 99, - top: 0, - }} - title={"Add Metadata"} - onClick={action(() => this.editingMetadata = !this.editingMetadata)} - /> - <div className="editableclass" onKeyPress={this.enter} style={{ opacity: isEditing ? 1 : 0, pointerEvents: isEditing ? "auto" : "none", transition: "0.4s opacity ease", position: "absolute", top: 0, left: 0, height: 20, width: "-webkit-fill-available" }}> - <EditableView - contents={this.query} - SetValue={this.updateKey} - GetValue={() => ""} - /> - </div> - </div > - ); - } - -}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionLinearView.scss b/src/client/views/collections/CollectionLinearView.scss index b8b72e756..f5c4299a9 100644 --- a/src/client/views/collections/CollectionLinearView.scss +++ b/src/client/views/collections/CollectionLinearView.scss @@ -2,7 +2,7 @@ @import "../_nodeModuleOverrides"; .collectionLinearView-outer { - overflow: hidden; + overflow: visible; height: 100%; .collectionLinearView { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index a1f5ff657..9ae72ba53 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,37 +1,35 @@ import React = require("react"); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, reaction, runInAction, Lambda } from "mobx"; +import { Tooltip } from "@material-ui/core"; +import { action, computed, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt, Field } from "../../../fields/Doc"; -import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types"; -import AntimodeMenu from "../AntimodeMenu"; -import "./CollectionMenu.scss"; -import { undoBatch } from "../../util/UndoManager"; -import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; -import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils"; -import { DragManager } from "../../util/DragManager"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { List } from "../../../fields/List"; -import { EditableView } from "../EditableView"; +import { ColorState } from "react-color"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { Document } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; -import { listSpec } from "../../../fields/Schema"; -import FormatShapePane from "./collectionFreeForm/FormatShapePane"; -import { ActiveFillColor, SetActiveInkWidth, ActiveInkColor, SetActiveBezierApprox, SetActiveArrowEnd, SetActiveArrowStart, SetActiveFillColor, SetActiveInkColor } from "../InkingStroke"; -import GestureOverlay from "../GestureOverlay"; import { InkTool } from "../../../fields/InkField"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Document } from "../../../fields/documentSchemas"; -import { SelectionManager } from "../../util/SelectionManager"; -import { DocumentView } from "../nodes/DocumentView"; -import { ColorState } from "react-color"; +import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; -import RichTextMenu from "../nodes/formattedText/RichTextMenu"; import { RichTextField } from "../../../fields/RichTextField"; +import { listSpec } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { DocUtils } from "../../documents/Documents"; -import { Tooltip } from "@material-ui/core"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils"; +import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { DragManager } from "../../util/DragManager"; +import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; +import AntimodeMenu from "../AntimodeMenu"; +import { EditableView } from "../EditableView"; +import GestureOverlay from "../GestureOverlay"; +import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { DocumentView } from "../nodes/DocumentView"; +import RichTextMenu from "../nodes/formattedText/RichTextMenu"; +import "./CollectionMenu.scss"; +import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; @observer export default class CollectionMenu extends AntimodeMenu { @@ -185,7 +183,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; }, }; - @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } + @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } @@ -236,7 +234,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @computed get subChrome() { switch (this.props.type) { - default: + default: return this.otherSubChrome; case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} />); case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" {...this.props} />); @@ -247,6 +245,21 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp case CollectionViewType.Docking: return (<CollectionDockingChrome key="collchrome" {...this.props} />); } } + + @computed get otherSubChrome() { + const docType = this.props.docView.Document.type; + switch (docType) { + default: return (null); + case DocumentType.IMG: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.PDF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.INK: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.WEB: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.VID: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={false} isDoc={true} />); + case DocumentType.RTF: return (<CollectionFreeFormViewChrome key="collchrome" {...this.props} isOverlay={this.props.type === CollectionViewType.Invalid} isDoc={true} />); + } + } + + private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer?.(); @@ -392,7 +405,7 @@ export class CollectionDockingChrome extends React.Component<CollectionMenuProps } @observer -export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean }> { +export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean, isDoc?: boolean }> { public static Instance: CollectionFreeFormViewChrome; constructor(props: any) { super(props); @@ -590,34 +603,36 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div>; } + @observable viewType = this.selectedDoc?._viewType; + render() { return !this.props.docView.layoutDoc ? (null) : <div className="collectionFreeFormMenu-cont"> - {this.props.docView.props.renderDepth !== 0 || this.isText ? (null) : + {this.props.docView.props.renderDepth !== 0 || this.isText || this.props.isDoc ? (null) : <Tooltip key="map" title={<div className="dash-tooltip">Toggle Mini Map</div>} placement="bottom"> - <div className="backKeyframe" onClick={this.miniMap}> + <div className="backKeyframe" onClick={this.miniMap} style={{ marginRight: "5px" }}> <FontAwesomeIcon icon={"map"} size={"lg"} /> </div> </Tooltip> } - {!this.isText ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> + {!this.isText && !this.props.isDoc ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> <div className="backKeyframe" onClick={this.prevKeyframe}> <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> </div> </Tooltip> : null} - {!this.isText ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> + {!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }} onClick={action(() => this.document.editing = !this.document.editing)} > {NumCast(this.document.currentFrame)} </div> </Tooltip> : null} - {!this.isText ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> + {!this.isText && !this.props.isDoc ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> <div className="fwdKeyframe" onClick={this.nextKeyframe}> <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> </div> </Tooltip> : null} - {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText ? (null) : + {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) : <Tooltip key="hypothesis" title={<div className="dash-tooltip">Use Hypothesis</div>} placement="bottom"> <button className={"antimodeMenu-button"} key="hypothesis" style={{ @@ -655,6 +670,24 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu getKeySuggestions = async (value: string): Promise<string[]> => { value = value.toLowerCase(); const docs = DocListCast(this.document[this.props.fieldKey]); + + if (Doc.UserDoc().noviceMode) { + if (docs instanceof Doc) { + const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || + key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || + (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); + return keys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + } else { + const keys = new Set<string>(); + docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); + const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || + key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || + key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] && + key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); + return noviceKeys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + } + } + if (docs instanceof Doc) { return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value)); } else { diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index eecaf7672..d11d6a5ba 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -32,6 +32,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { DateField } from "../../../fields/DateField"; +import { RichTextField } from "../../../fields/RichTextField"; const path = require('path'); library.add(faExpand); @@ -193,7 +194,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); const onItemDown = (e: React.PointerEvent) => { - fieldIsDoc && SetupDrag(this._focusRef, + //fieldIsDoc && + SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); @@ -240,25 +242,72 @@ export class CollectionSchemaCell extends React.Component<CellProps> { // <FontAwesomeIcon icon="expand" size="sm" /> // </div> // ); - trace(); - - - + const positions = []; + if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { + const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); + let term = ""; + if (cfield !== undefined) { + if (cfield.Text !== undefined) { + term = cfield.Text; + } + else if (StrCast(cfield)) { + term = StrCast(cfield); + } + else { + term = String(NumCast(cfield)); + } + } + term = term.toLowerCase(); + const search = StrCast(this.props.Document._searchString).toLowerCase(); + let start = term.indexOf(search); + let tally = 0; + if (start !== -1) { + positions.push(start); + } + while (start < contents.length && start !== -1) { + term = term.slice(start + search.length + 1); + tally += start + search.length + 1; + start = term.indexOf(search); + positions.push(tally + start); + } + if (positions.length > 1) { + positions.pop(); + } + } return ( - <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> + <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} + ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}> <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}> - - <EditableView + positions={positions.length > 0 ? positions : undefined} + search={StrCast(this.props.Document._searchString) ? StrCast(this.props.Document._searchString) : undefined} editing={this._isEditing} isEditingCallback={this.isEditingCallback} display={"inline"} contents={contents ? contents : type === "number" ? "0" : "undefined"} + highlight={positions.length > 0 ? true : undefined} //contents={StrCast(contents)} height={"auto"} maxHeight={Number(MAX_ROW_HEIGHT)} placeholder={"enter value"} + bing={() => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); + if (cfield !== undefined) { + console.log(typeof (cfield)); + // if (typeof(cfield)===RichTextField) + const a = cfield as RichTextField; + if (a.Text !== undefined) { + return (a.Text); + } + else if (StrCast(cfield)) { + return StrCast(cfield); + } + else { + return String(NumCast(cfield)); + } + } + }} GetValue={() => { if (type === "number" && (contents === 0 || contents === "0")) { return "0"; @@ -272,6 +321,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; return val; + } }} @@ -827,3 +877,69 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { ); } } + + +@observer +export class CollectionSchemaButtons extends CollectionSchemaCell { + + render() { + // const reference = React.createRef<HTMLDivElement>(); + // const onItemDown = (e: React.PointerEvent) => { + // (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined : + // SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); + // }; + const doc = this.props.rowProps.original; + let buttons: JSX.Element | undefined = undefined; + buttons = <div style={{ + paddingTop: 8, + paddingLeft: 3, + }}><button onClick={() => { + doc.searchMatch = false; + setTimeout(() => doc.searchMatch = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + }} style={{ padding: 2, left: 77 }}> + <FontAwesomeIcon icon="arrow-up" size="sm" /> + </button> + <button onClick={() => { + { + doc.searchMatchAlt = false; + setTimeout(() => doc.searchMatchAlt = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + } + }} style={{ padding: 2 }}> + <FontAwesomeIcon icon="arrow-down" size="sm" /> + </button></div>; + const type = StrCast(doc.type); + if (type === "pdf") { + buttons = <div><button + style={{ + position: "relative", + height: 30, + width: 28, + left: 1, + }} + + onClick={() => { + doc.searchMatch = false; + setTimeout(() => doc.searchMatch = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + }}> + <FontAwesomeIcon icon="arrow-down" size="sm" /> + </button></div >; + } + else if (type !== "rtf") { + buttons = undefined; + } + + if (BoolCast(this.props.Document._searchDoc) === true) { + + } + else { + buttons = undefined; + } + return ( + <div> {buttons}</div> + ); + } +} + diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index efff4db98..e65adcf76 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -9,6 +9,8 @@ import { ColumnType } from "./CollectionSchemaView"; import { faFile } from "@fortawesome/free-regular-svg-icons"; import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; +import { Doc } from "../../../fields/Doc"; +import { StrCast } from "../../../fields/Types"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -76,14 +78,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe - - - - - - - - export interface ColumnMenuProps { columnField: SchemaHeaderField; // keyValue: string; @@ -288,9 +282,10 @@ export interface KeysDropdownProps { existingKeys: string[]; canAddNew: boolean; addNew: boolean; - onSelect: (oldKey: string, newKey: string, addnew: boolean) => void; + onSelect: (oldKey: string, newKey: string, addnew: boolean, filter?: string) => void; setIsEditing: (isEditing: boolean) => void; width?: string; + docs?: Doc[]; } @observer export class KeysDropdown extends React.Component<KeysDropdownProps> { @@ -306,10 +301,23 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @action onSelect = (key: string): void => { - this.props.onSelect(this._key, key, this.props.addNew); - this.setKey(key); + if (key.slice(0, this._key.length) === this._key && this._key !== key) { + const filter = key.slice(this._key.length - key.length); + this.props.onSelect(this._key, this._key, this.props.addNew, filter); + } + else { + this.props.onSelect(this._key, key, this.props.addNew); + this.setKey(key); + this._isOpen = false; + this.props.setIsEditing(false); + } + } + + @action + onSelect2 = (key: string): void => { + this._searchTerm = this._searchTerm.slice(0, this._key.length) + key; this._isOpen = false; - this.props.setIsEditing(false); + } @undoBatch @@ -371,22 +379,53 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { }); // if search term does not already exist as a group type, give option to create new group type - if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { - options.push(<div key={""} className="key-option" style={{ + if (this._key !== this._searchTerm.slice(0, this._key.length)) { + if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { + options.push(<div key={""} className="key-option" style={{ + border: "1px solid lightgray", + width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + }} + onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> + Create "{this._searchTerm}" key</div>); + } + } + + return options; + } + + renderFilterOptions = (): JSX.Element[] | JSX.Element => { + if (!this._isOpen) return <></>; + const keyOptions: string[] = []; + const temp = this._searchTerm.slice(this._key.length); + this.props.docs?.forEach((doc) => { + const key = StrCast(doc[this._key]); + if (keyOptions.includes(key) === false && key.includes(temp)) { + keyOptions.push(key); + } + }); + + + const options = keyOptions.map(key => { + return <div key={key} className="key-option" style={{ border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" }} - onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> - Create "{this._searchTerm}" key</div>); - } + onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect2(key); }}>{key}</div>; + }); return options; } + render() { return ( - <div className="keys-dropdown" style={{ width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" }}> - <input className="keys-search" //style={{ width: this.props.width, maxWidth: "1000" }} + <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> + {this._key === this._searchTerm.slice(0, this._key.length) ? + <div style={{ position: "absolute", marginLeft: "4px", marginTop: "3", color: "grey", pointerEvents: "none", lineHeight: 1.15 }}> + {this._key} + </div> + : undefined} + <input className="keys-search" style={{ width: "100%" }} ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} onChange={e => this.onChange(e.target.value)} onClick={(e) => { @@ -395,10 +434,11 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { }} onFocus={this.onFocus} onBlur={this.onBlur}></input> <div className="keys-options-wrapper" style={{ backgroundColor: "white", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + width: this.props.width, maxWidth: this.props.width, }} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> - {this.renderOptions()} + {this._key === this._searchTerm.slice(0, this._key.length) ? + this.renderFilterOptions() : this.renderOptions()} </div> </div > ); diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index b77173b25..dade4f2f2 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -209,6 +209,14 @@ export class MovableRow extends React.Component<MovableRowProps> { return doc !== targetCollection && doc !== targetView?.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); } + @action + onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { + console.log("yes"); + if (e.key === "Backspace" || e.key === "Delete") { + undoBatch(() => this.props.removeDoc(this.props.rowInfo.original)); + } + } + render() { const { children = null, rowInfo } = this.props; if (!rowInfo) { @@ -227,14 +235,14 @@ export class MovableRow extends React.Component<MovableRowProps> { if (this.props.rowWrapped) className += " row-wrapped"; return ( - <div className={className} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}> - <div className="collectionSchema-row-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - <ReactTableDefaults.TrComponent> - <div className="row-dragger"> + <div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}> + <div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} > + {/* <div className="row-dragger"> <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div> <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div> <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div> - </div> + </div> */} {children} </ReactTableDefaults.TrComponent> </div> diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 5226a60f1..ba0a259c5 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -6,7 +6,7 @@ border-style: solid; border-radius: $border-radius; box-sizing: border-box; - position: absolute; + position: relative; top: 0; width: 100%; height: 100%; @@ -25,7 +25,6 @@ .collectionSchemaView-tableContainer { width: 100%; height: 100%; - overflow: scroll; } .collectionSchemaView-dividerDragger { @@ -59,9 +58,7 @@ } .rt-thead { - width: calc(100% - 52px); - margin-left: 50px; - + width: 100%; z-index: 100; overflow-y: visible; @@ -96,7 +93,7 @@ } .rt-tbody { - width: calc(100% - 2px); + width: 100%; direction: rtl; overflow: visible; } @@ -164,16 +161,6 @@ .collectionSchema-col { height: 100%; - - .collectionSchema-col-wrapper { - &.col-before { - border-left: 2px solid red; - } - - &.col-after { - border-right: 2px solid red; - } - } } @@ -297,7 +284,6 @@ button.add-column { background-color: white; border: 1px solid lightgray; padding: 2px 3px; - overflow-x: hidden; &:not(:first-child) { border-top: 0; @@ -525,14 +511,21 @@ button.add-column { .collectionSchemaView-table { width: 100%; height: 100%; - overflow: visible; } .reactTable-sub { padding: 10px 30px; background-color: rgb(252, 252, 252); - width: calc(100% - 50px); - margin-left: 50px; + width: 100%; + + .rt-thead { + display:none; + } + .collectionSchemaView-table{ + border: solid 1px; + overflow: hidden; + } + .row-dragger { background-color: rgb(252, 252, 252); @@ -567,7 +560,6 @@ button.add-column { text-transform: uppercase; cursor: pointer; font-size: 10.5px; - padding: 10px; margin-left: 50px; margin-top: 10px; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index f67e049fd..a003de0d3 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -4,27 +4,26 @@ import { faCog, faPlus, faSortDown, faSortUp, faTable } from '@fortawesome/free- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; +import Measure from "react-measure"; import { Resize } from "react-table"; import "react-table/react-table.css"; import { Doc } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { Docs, DocumentOptions } from "../../documents/Documents"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { Cast, NumCast } from "../../../fields/Types"; +import { TraceMobx } from "../../../fields/util"; +import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils"; +import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; import '../DocumentDecorations.scss'; +import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; import { KeysDropdown } from "./CollectionSchemaHeaders"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { setupMoveUpEvents, emptyFunction, returnZero, returnOne, returnFalse } from "../../../Utils"; -import { SnappingManager } from "../../util/SnappingManager"; -import Measure from "react-measure"; import { SchemaTable } from "./SchemaTable"; -import { TraceMobx } from "../../../fields/util"; library.add(faCog, faPlus, faSortUp, faSortDown); library.add(faTable); @@ -170,6 +169,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => { const columns = this.columns; + columns.forEach(col => col.setDesc(undefined)); + const index = columns.findIndex(c => c.heading === columnField.heading); const column = columns[index]; column.setDesc(descending); @@ -310,7 +311,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @undoBatch @action - changeColumns = (oldKey: string, newKey: string, addNew: boolean) => { + changeColumns = (oldKey: string, newKey: string, addNew: boolean, filter?: string) => { const columns = this.columns; if (columns === undefined) { this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]); @@ -325,6 +326,20 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { column.setHeading(newKey); columns[index] = column; this.columns = columns; + if (filter) { + Doc.setDocFilter(this.props.Document, newKey, filter, "match"); + if (this.props.Document.selectedDoc !== undefined) { + const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc; + Doc.setDocFilter(doc, newKey, filter, "match"); + } + } + else { + this.props.Document._docFilters = undefined; + if (this.props.Document.selectedDoc !== undefined) { + const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc; + doc._docFilters = undefined; + } + } } } } @@ -591,6 +606,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { } } + + + onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { + } render() { TraceMobx(); const menuContent = this.renderMenuContent; @@ -608,14 +627,15 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>} </Measure> </div>; - return <div className="collectionSchemaView-container" style={{ + overflow: this.props.overflow === true ? "auto" : undefined, pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, - width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%" + width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" - style={{ width: `calc(100% - ${this.previewWidth()}px)` }} + style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }} + onKeyPress={this.onKeyPress} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index fbc4e1552..f193a9787 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -369,7 +369,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC </div> {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled' && type !== DocumentType.PRES) ? <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton" - style={{ width: style.columnWidth / style.numGroupColumns }}> + style={{ width: style.columnWidth / style.numGroupColumns, marginBottom: 70 }}> <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} /> </div> : null} </div> diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 3ebc6baca..4025e25f9 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,7 +1,7 @@ import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import { basename } from 'path'; import CursorField from "../../../fields/CursorField"; -import { Doc, Opt, Field } from "../../../fields/Doc"; +import { Doc, Opt, Field, DocListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; @@ -112,9 +112,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])]; } @computed get childDocs() { + let rawdocs: (Doc | Promise<Doc>)[] = DocListCast(this.props.Document._searchDocs); - let rawdocs: (Doc | Promise<Doc>)[] = []; - if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; + if (rawdocs.length !== 0) { + } else if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; rawdocs = [this.dataField]; } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. rawdocs = Cast(this.dataField, listSpec(Doc), null); @@ -126,11 +127,66 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: } const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); + const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); + let childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + + const searchDocs = DocListCast(this.props.Document._searchDocs); + // if (searchDocs !== undefined && searchDocs.length > 0) { + // let newdocs: Doc[] = []; + // childDocs.forEach((el) => { + // searchDocs.includes(el) ? newdocs.push(el) : undefined; + // }); + // childDocs = newdocs; + // } + + let docsforFilter: Doc[] = childDocs; + if (searchDocs !== undefined && searchDocs.length > 0) { + docsforFilter = []; + // let newdocs: Doc[] = []; + // let newarray: Doc[] = []; + //while (childDocs.length > 0) { + //newarray = []; + childDocs.forEach((d) => { + if (d.data !== undefined) { + console.log(d); + let newdocs = DocListCast(d.data); + if (newdocs.length > 0) { + let vibecheck: boolean | undefined = undefined; + let newarray: Doc[] = []; + while (newdocs.length > 0) { + newarray = []; + newdocs.forEach((t) => { + if (d.data !== undefined) { + const newdocs = DocListCast(t.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + if (searchDocs.includes(t)) { + vibecheck = true; + } + }); + newdocs = newarray; + } + if (vibecheck === true) { + docsforFilter.push(d); + } + } + } + if (searchDocs.includes(d)) { + docsforFilter.push(d); + } + }); + //childDocs = newarray; + //} + } + childDocs = docsforFilter; + + const docFilters = this.docFilters(); - const viewSpecScript = ScriptCast(this.props.Document.viewSpecScript); const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, docFilters, docRangeFilters, viewSpecScript); + return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, this.docFilters(), docRangeFilters, viewSpecScript); } @action @@ -436,4 +492,3 @@ import { CollectionView, CollectionViewType } from "./CollectionView"; import { SelectionManager } from "../../util/SelectionManager"; import { OverlayView } from "../OverlayView"; import { setTimeout } from "timers"; - diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 50f0534bd..c9bf82406 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -22,7 +22,7 @@ ul { list-style: none; padding-left: 20px; - margin-bottom: 1px;// otherwise vertical scrollbars may pop up for no apparent reason.... + margin-bottom: 1px; // otherwise vertical scrollbars may pop up for no apparent reason.... } @@ -35,7 +35,7 @@ width: 15px; color: $intermediate-color; margin-top: 3px; - transform: scale(1.3, 1.3); + transform: scale(1.3, 1.3); border: #80808030 1px solid; border-radius: 4px; } @@ -67,8 +67,10 @@ margin-left: 3px; display: none; } + .collectionTreeView-keyHeader:hover { background: #797777; + cursor: pointer; } .collectionTreeView-subtitle { @@ -89,8 +91,10 @@ height: 17px; width: 15px; } + .treeViewItem-openRight:hover { background: #797777; + cursor: pointer; } .treeViewItem-border { @@ -106,10 +110,12 @@ .editableView-container-editing-oneLine { min-width: 15px; } + .documentView-node-topmost { width: unset; } - > svg { + + >svg { display: none; } @@ -119,7 +125,8 @@ .collectionTreeView-keyHeader { display: inherit; } - > svg { + + >svg { display: inherit; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 89034a0c0..6e15cb887 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -74,7 +74,7 @@ export enum CollectionViewType { Pile = "pileup" } export interface CollectionViewCustomProps { - filterAddDocument: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + filterAddDocument?: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) childLayoutTemplate?: () => Opt<Doc>; // specify a layout Doc template to use for children of the collection childLayoutString?: string; // specify a layout string to use for children of the collection childOpacity?: () => number; @@ -88,6 +88,7 @@ export interface CollectionRenderProps { active: () => boolean; whenActiveChanged: (isActive: boolean) => void; PanelWidth: () => number; + PanelHeight: () => number; ChildLayoutTemplate?: () => Doc; ChildLayoutString?: string; } @@ -523,6 +524,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus </div> <div className="collectionTimeView-tree" key="tree"> <CollectionTreeView + PanelPosition={""} Document={facetCollection} DataDoc={facetCollection} fieldKey={`${this.props.fieldKey}-filter`} @@ -574,6 +576,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus active: this.active, whenActiveChanged: this.whenActiveChanged, PanelWidth: this.bodyPanelWidth, + PanelHeight: this.props.PanelHeight, ChildLayoutTemplate: this.childLayoutTemplate, ChildLayoutString: this.childLayoutString, }; @@ -582,7 +585,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return (<div className={"collectionView"} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.props.Document.isBackground ? "none" : undefined, boxShadow }}> {this.showIsTagged()} - <div className="collectionView-facetCont" style={{ width: `calc(100% - ${this.facetWidth()}px)` }}> + <div className="collectionView-facetCont" style={{ display: this.props.PanelPosition === "absolute" ? "flex" : "", justifyContent: this.props.PanelPosition === "absolute" ? "center" : "", width: `calc(100% - ${this.facetWidth()}px)` }}> {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} </div> {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index 7e2840c2c..75d484cbe 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -11,22 +11,22 @@ import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../fields/Types"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; +import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils"; import { Docs, DocumentOptions } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; import { ContextMenu } from "../ContextMenu"; import '../DocumentDecorations.scss'; -import { CellProps, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaDateCell } from "./CollectionSchemaCells"; +import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; +import { CellProps, CollectionSchemaButtons, CollectionSchemaCell, CollectionSchemaCheckboxCell, CollectionSchemaDateCell, CollectionSchemaDocCell, CollectionSchemaImageCell, CollectionSchemaListCell, CollectionSchemaNumberCell, CollectionSchemaStringCell } from "./CollectionSchemaCells"; import { CollectionSchemaAddColumnHeader, KeysDropdown } from "./CollectionSchemaHeaders"; import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC"; import "./CollectionSchemaView.scss"; import { CollectionView } from "./CollectionView"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { emptyFunction, returnZero, returnOne, returnFalse, returnEmptyFilter, emptyPath } from "../../../Utils"; -import { TouchScrollableMenuItem } from "../TouchScrollableMenu"; enum ColumnType { @@ -128,7 +128,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } @computed get sorted(): SortingRule[] { return this.props.columns.reduce((sorted, shf) => { - shf.desc && sorted.push({ id: shf.heading, desc: shf.desc }); + shf.desc !== undefined && sorted.push({ id: shf.heading, desc: shf.desc }); return sorted; }, [] as SortingRule[]); } @@ -160,7 +160,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const focusedCol = this._focusedCell.col; const isEditable = !this.props.headerIsEditing; - if (this.childDocs.reduce((found, doc) => found || doc.type === "collection", false)) { + if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) { columns.push( { expander: true, @@ -177,6 +177,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } ); } + console.log(columns); const cols = this.props.columns.map(col => { @@ -188,7 +189,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { addNew={false} onSelect={this.props.changeColumns} setIsEditing={this.props.setHeaderIsEditing} - + docs={this.props.childDocs} // try commenting this out width={"100%"} />; @@ -216,21 +217,21 @@ export class SchemaTable extends React.Component<SchemaTableProps> { className="collectionSchemaView-menuOptions-wrapper" style={{ background: col.color, padding: "2px", - display: "flex" + display: "flex", cursor: "default", height: "100%", }}> - <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingLeft: "7px" }} /> - <div className="keys-dropdown" - style={{ display: "inline", zIndex: 1000 }}> - {keysDropdown} - </div> + <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px" }} /> + {/* <div className="keys-dropdown" + style={{ display: "inline", zIndex: 1000 }}> */} + {keysDropdown} + {/* </div> */} <div onClick={e => this.changeSorting(col)} - style={{ paddingRight: "6px", marginLeft: "4px", display: "inline" }}> - <FontAwesomeIcon icon={sortIcon} size="sm" /> - </div> - <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} - style={{ float: "right", paddingRight: "6px" }}> - <FontAwesomeIcon icon={"cog"} size="sm" /> + style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit" }}> + <FontAwesomeIcon icon={sortIcon} size="lg" /> </div> + {/* <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} + style={{ float: "right", paddingRight: "6px", zIndex: 1, background: "inherit" }}> + <FontAwesomeIcon icon={"compass"} size="sm" /> + </div> */} </div>; return { @@ -283,13 +284,63 @@ export class SchemaTable extends React.Component<SchemaTableProps> { Header: <CollectionSchemaAddColumnHeader createColumn={this.createColumn} />, accessor: (doc: Doc) => 0, id: "add", - Cell: (rowProps: CellInfo) => <></>, + Cell: (rowProps: CellInfo) => { + const rowIndex = rowProps.index; + const columnIndex = this.props.columns.map(c => c.heading).indexOf(rowProps.column.id!); + const isFocused = focusedRow === rowIndex && focusedCol === columnIndex && tableIsFocused; + const props: CellProps = { + row: rowIndex, + col: columnIndex, + rowProps: rowProps, + isFocused: isFocused, + changeFocusedCellByIndex: this.changeFocusedCellByIndex, + CollectionView: this.props.CollectionView, + ContainingCollection: this.props.ContainingCollectionView, + Document: this.props.Document, + fieldKey: this.props.fieldKey, + renderDepth: this.props.renderDepth, + addDocTab: this.props.addDocTab, + pinToPres: this.props.pinToPres, + moveDocument: this.props.moveDocument, + setIsEditing: this.setCellIsEditing, + isEditable: isEditable, + setPreviewDoc: this.props.setPreviewDoc, + setComputed: this.setComputed, + getField: this.getField, + showDoc: this.showDoc, + }; + + return <CollectionSchemaButtons {...props} />; + }, width: 28, resizable: false }); + console.log(columns); return columns; } + + + @action + nextHighlight = (e: React.MouseEvent, doc: Doc) => { + e.preventDefault(); + e.stopPropagation(); + doc.searchMatch = false; + console.log(doc.searchMatch); + setTimeout(() => doc.searchMatch = true, 0); + console.log(doc.searchMatch); + + doc.searchIndex = NumCast(doc.searchIndex); + } + + @action + nextHighlight2 = (doc: Doc) => { + + doc.searchMatchAlt = false; + setTimeout(() => doc.searchMatchAlt = true, 0); + doc.searchIndex = NumCast(doc.searchIndex); + } + constructor(props: SchemaTableProps) { super(props); // convert old schema columns (list of strings) into new schema columns (list of schema header fields) @@ -574,7 +625,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { return <div className="collectionSchemaView-table" onPointerDown={this.props.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > {this.reactTable} - <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div> + {StrCast(this.props.Document.type) !== "search" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div> + : undefined} {!this._showDoc ? (null) : <div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }} style={{ diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 65cd28742..f2ad0ba58 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1258,7 +1258,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" }); - appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); + optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss index 89124129b..5b41db90e 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -41,7 +41,7 @@ font-size: 12.5px; &:hover { - cursor: pointer; + cursor: text; } } @@ -617,6 +617,7 @@ display: flex; margin-bottom: 3px; + margin-left: 4px; .arrows-head { @@ -650,7 +651,7 @@ .dashed { display: flex; - margin-left: 74px; + margin-left: 64px; margin-bottom: 6px; .dashed-title { @@ -662,4 +663,15 @@ margin-top: 2px; } } +} + +.editable-title { + border: none; + padding: 6px; + padding-bottom: 2px; + + + &:hover { + border: 0.75px solid rgb(122, 28, 28); + } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index dcbf8e989..89f48fc65 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -29,6 +29,9 @@ import "./FormatShapePane.scss"; import { discovery_v1 } from "googleapis"; import { PresBox } from "../../nodes/PresBox"; import { DocumentManager } from "../../../util/DocumentManager"; +const higflyout = require("@hig/flyout"); +export const { anchorPoints } = higflyout; +export const Flyout = higflyout.default; interface PropertiesViewProps { @@ -74,6 +77,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @observable openPresProgressivize: boolean = true; @observable openSlideOptions: boolean = true; + @observable inActions: boolean = false; @observable _controlBtn: boolean = false; @observable _lock: boolean = false; @@ -174,7 +178,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); const rows: JSX.Element[] = []; for (const key of Object.keys(ids).slice().sort()) { - if ((key[0] === key[0].toUpperCase() && key.substring(0, 3) !== "ACL") + if ((key[0] === key[0].toUpperCase() && key.substring(0, 3) !== "ACL" && key !== "UseCors") || key[0] === "#" || key === "author" || key === "creationDate" || key.indexOf("lastModified") !== -1) { @@ -254,7 +258,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { rootSelected={returnFalse} treeViewDoc={undefined} backgroundColor={() => "lightgrey"} - fitToBox={false} + fitToBox={true} FreezeDimensions={true} NativeWidth={layoutDoc.type === StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero} @@ -277,6 +281,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { bringToFront={returnFalse} ContentScaling={returnOne} dontRegisterView={true} + dropAction={undefined} /> </div>; } else { @@ -367,20 +372,19 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { />; } - @undoBatch @action toggleCheckbox = () => { this.layoutFields = !this.layoutFields; } @computed get editableTitle() { - return <EditableView + return <div className="editable-title"><EditableView key="editableView" contents={StrCast(this.selectedDoc?.title)} height={25} fontSize={14} GetValue={() => StrCast(this.selectedDoc?.title)} - SetValue={this.setTitle} />; + SetValue={this.setTitle} /> </div>; } @undoBatch @@ -417,15 +421,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { var index = 0; if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { doc.rotation = Number(doc.rotation) + Number(angle); - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - + const inks = Cast(doc.data, InkField)?.inkData; + if (inks) { const newPoints: { X: number, Y: number }[] = []; - for (var i = 0; i < ink.length; i++) { - const newX = Math.cos(angle) * (ink[i].X - _centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X; - const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y; + inks.forEach(ink => { + const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; + const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; newPoints.push({ X: newX, Y: newY }); - } + }); doc.data = new InkField(newPoints); const xs = newPoints.map(p => p.X); const ys = newPoints.map(p => p.Y); @@ -552,7 +555,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { getField(key: string) { //if (this.selectedDoc) { - return Field.toString(this?.[key] as Field); + return Field.toString(this.selectedDoc?.[key] as Field); // } else { // return undefined as Opt<string>; // } @@ -594,14 +597,18 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } - colorButton(value: string, setter: () => {}) { - return <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}> - <div className="color-button-preview" style={{ - backgroundColor: value ?? "121212", width: 15, height: 15, - display: value === "" || value === "transparent" ? "none" : "" - }} /> - {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14, position: "fixed" }}>☒</p> : ""} - </div>; + colorButton(value: string, type: string, setter: () => {}) { + return <Flyout anchorPoint={anchorPoints.LEFT_TOP} + content={type === "fill" ? this.fillPicker : this.linePicker}> + <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}> + <div className="color-button-preview" style={{ + backgroundColor: value ?? "121212", width: 15, height: 15, + display: value === "" || value === "transparent" ? "none" : "" + }} /> + {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14, position: "fixed" }}>☒</p> : ""} + </div> + </Flyout>; + } @undoBatch @@ -627,8 +634,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { color={type === "stk" ? this.colorStk : this.colorFil} />; } - @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } - @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } + @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } + @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } @@ -645,8 +652,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="stroke-button">{this.lineButton}</div> </div> </div> - {this._fillBtn ? this.fillPicker : ""} - {this._lineBtn ? this.linePicker : ""} + {/* {this._fillBtn ? this.fillPicker : ""} + {this._lineBtn ? this.linePicker : ""} */} </div>; } @@ -753,201 +760,203 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return <div className="propertiesView" style={{ width: this.props.width }}> <div className="propertiesView-title" style={{ width: this.props.width }}> No Document Selected - <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> - <FontAwesomeIcon icon="times" color="black" size="xs" /> - </div> </div> </div>; - } - const novice = Doc.UserDoc().noviceMode; - - if (this.selectedDoc && !this.isPres) { - return <div className="propertiesView" style={{ width: this.props.width }} > - <div className="propertiesView-title" style={{ width: this.props.width }}> - Properties - <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + } else { + const novice = Doc.UserDoc().noviceMode; + + if (this.selectedDoc && !this.isPres) { + return <div className="propertiesView" style={{ + width: this.props.width, + // overflowY: this.inActions ? "visible" : "scroll" + }} > + <div className="propertiesView-title" style={{ width: this.props.width }}> + Properties + {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div> */} </div> - </div> - <div className="propertiesView-name"> - {this.editableTitle} - </div> - <div className="propertiesView-settings"> - <div className="propertiesView-settings-title" - onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })} - style={{ backgroundColor: this.openActions ? "black" : "" }}> - Actions - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-name"> + {this.editableTitle} + </div> + <div className="propertiesView-settings" onPointerEnter={() => runInAction(() => { this.inActions = true; })} + onPointerLeave={action(() => this.inActions = false)}> + <div className="propertiesView-settings-title" + onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })} + style={{ backgroundColor: this.openActions ? "black" : "" }}> + Actions + <div className="propertiesView-settings-title-icon"> + <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {!this.openActions ? (null) : + <div className="propertiesView-settings-content"> + <PropertiesButtons /> + </div>} </div> - {!this.openActions ? (null) : - <div className="propertiesView-settings-content"> - <PropertiesButtons /> - </div>} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { 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 className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { 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"> + {this.sharingTable} + </div>} </div> - {!this.openSharing ? (null) : - <div className="propertiesView-sharing-content"> - {this.sharingTable} + + {!this.isInk ? (null) : + <div className="propertiesView-appearance"> + <div className="propertiesView-appearance-title" + onPointerDown={() => runInAction(() => { 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>} - </div> - {!this.isInk ? (null) : - <div className="propertiesView-appearance"> - <div className="propertiesView-appearance-title" - onPointerDown={() => runInAction(() => { 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" /> + {this.isInk ? <div className="propertiesView-transform"> + <div className="propertiesView-transform-title" + onPointerDown={() => runInAction(() => { 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.openAppearance ? (null) : - <div className="propertiesView-appearance-content"> - {this.appearanceEditor} - </div>} - </div>} - - {this.isInk ? <div className="propertiesView-transform"> - <div className="propertiesView-transform-title" - onPointerDown={() => runInAction(() => { 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} + {this.openTransform ? <div className="propertiesView-transform-content"> + {this.transformEditor} + </div> : null} </div> : null} - </div> : null} - - <div className="propertiesView-fields"> - <div className="propertiesView-fields-title" - onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })} - style={{ backgroundColor: this.openFields ? "black" : "" }}> - <div className="propertiesView-fields-title-name"> - Fields {"&"} Tags - <div className="propertiesView-fields-title-icon"> - <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" /> + + <div className="propertiesView-fields"> + <div className="propertiesView-fields-title" + onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })} + style={{ backgroundColor: this.openFields ? "black" : "" }}> + <div className="propertiesView-fields-title-name"> + Fields {"&"} Tags + <div className="propertiesView-fields-title-icon"> + <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </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> - {!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-layout"> - <div className="propertiesView-layout-title" - onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })} - style={{ backgroundColor: this.openLayout ? "black" : "" }}> - Layout - <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}> - <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-layout"> + <div className="propertiesView-layout-title" + onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })} + style={{ backgroundColor: this.openLayout ? "black" : "" }}> + Layout + <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}> + <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.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null} - </div> - </div>; - } - if (this.isPres) { - return <div className="propertiesView" style={{ width: this.props.width }} > - <div className="propertiesView-title" style={{ width: this.props.width }}> - Presentation - <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> - <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div>; + } + if (this.isPres) { + return <div className="propertiesView" style={{ width: this.props.width }} > + <div className="propertiesView-title" style={{ width: this.props.width }}> + Presentation + <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + <FontAwesomeIcon icon="times" color="black" size="sm" /> + </div> </div> - </div> - <div className="propertiesView-name"> - {this.editableTitle} - <div className="propertiesView-presSelected"> - {PresBox.Instance._selectedArray.length} selected - <div className="propertiesView-selectedList"> - {PresBox.Instance.listOfSelected} + <div className="propertiesView-name"> + {this.editableTitle} + <div className="propertiesView-presSelected"> + {PresBox.Instance._selectedArray.length} selected + <div className="propertiesView-selectedList"> + {PresBox.Instance.listOfSelected} + </div> </div> </div> - </div> - <div className="propertiesView-settings"> - <div className="propertiesView-settings-title" - onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} - style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> - <FontAwesomeIcon icon={"plus"} /> Add new slide - <div className="propertiesView-settings-title-icon"> - <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-settings"> + <div className="propertiesView-settings-title" + onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })} + style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> + <FontAwesomeIcon icon={"plus"} /> Add new slide + <div className="propertiesView-settings-title-icon"> + <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openAddSlide ? <div className="propertiesView-settings-content"> + {PresBox.Instance.newDocumentDropdown} + </div> : null} </div> - {this.openAddSlide ? <div className="propertiesView-settings-content"> - {PresBox.Instance.newDocumentDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} - style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> - <FontAwesomeIcon icon={"rocket"} /> Transitions - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} + style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> + <FontAwesomeIcon icon={"rocket"} /> Transitions + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openPresTransitions ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.transitionDropdown} + </div> : null} </div> - {this.openPresTransitions ? <div className="propertiesView-sharing-content"> - {PresBox.Instance.transitionDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })} - style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> - <FontAwesomeIcon icon={"tasks"} /> Progressivize - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })} + style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> + <FontAwesomeIcon icon={"tasks"} /> Progressivize + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openPresProgressivize ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.progressivizeDropdown} + </div> : null} </div> - {this.openPresProgressivize ? <div className="propertiesView-sharing-content"> - {PresBox.Instance.progressivizeDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })} - style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> - <FontAwesomeIcon icon={"cog"} /> {PresBox.Instance.stringType} options - <div className="propertiesView-sharing-title-icon"> - <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> + <div className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })} + style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> + <FontAwesomeIcon icon={"cog"} /> {PresBox.Instance.stringType} options + <div className="propertiesView-sharing-title-icon"> + <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> </div> + {this.openSlideOptions ? <div className="propertiesView-sharing-content"> + {PresBox.Instance.optionsDropdown} + </div> : null} </div> - {this.openSlideOptions ? <div className="propertiesView-sharing-content"> - {PresBox.Instance.optionsDropdown} - </div> : null} - </div> - <div className="propertiesView-sharing"> - <div className="propertiesView-sharing-title" - onPointerDown={() => runInAction(() => { 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 className="propertiesView-sharing"> + <div className="propertiesView-sharing-title" + onPointerDown={() => runInAction(() => { 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 ? <div className="propertiesView-sharing-content"> + {this.sharingTable} + </div> : null} </div> - {this.openSharing ? <div className="propertiesView-sharing-content"> - {this.sharingTable} - </div> : null} - </div> - </div>; + </div>; + } } } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 21f77e47b..e6ac7021a 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -240,7 +240,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { w: Math.min(w, this.numCols), // reduces width if greater than numCols static: BoolCast(this.childLayoutPairs.find(({ layout }) => layout[Id] === i)?.layout.lockedPosition, false) // checks if the lock position item has been selected in the context menu })) : - this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index))); + this.savedLayoutList.map((layout, index) => { Object.assign(layout, this.unflexedPosition(index)); return layout; }); } /** diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss index d26b7920a..7e6999cdc 100644 --- a/src/client/views/linking/LinkEditor.scss +++ b/src/client/views/linking/LinkEditor.scss @@ -89,7 +89,7 @@ /* float: right; */ border-radius: 7px; font-size: 9px; - background-color: black; + background: black; /* padding: 3px; */ padding-top: 4px; padding-left: 7px; @@ -100,6 +100,7 @@ &:hover { cursor: pointer; + background: grey; } } } diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 660afd4b9..75fc8bf85 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -287,7 +287,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { @observable openDropdown: boolean = false; @observable showInfo: boolean = false; @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } - @observable private buttonColor: string = "black"; + @observable private buttonColor: string = ""; //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; @@ -303,7 +303,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { if (LinkManager.currentLink) { LinkManager.currentLink.description = value; this.buttonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.buttonColor = "black"), 750); + setTimeout(action(() => this.buttonColor = ""), 750); return true; } } @@ -345,7 +345,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> { ></input> </div> <div className="linkEditor-description-add-button" - style={{ backgroundColor: this.buttonColor }} + style={{ background: this.buttonColor }} onPointerDown={this.onDown}>Set</div> </div></div>; } diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 6feb0a398..f4aed94e7 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -191,7 +191,6 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { case DocumentType.AUDIO: destinationIcon = "microphone"; break; case DocumentType.BUTTON: destinationIcon = "bolt"; break; case DocumentType.PRES: destinationIcon = "tv"; break; - case DocumentType.QUERY: destinationIcon = "search"; break; case DocumentType.SCRIPTING: destinationIcon = "terminal"; break; case DocumentType.IMPORT: destinationIcon = "cloud-upload-alt"; break; case DocumentType.DOCHOLDER: destinationIcon = "expand"; break; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e3f258b8d..2408b3906 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -25,7 +25,7 @@ import { ImageBox } from "./ImageBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { PresBox } from "./PresBox"; -import { QueryBox } from "./QueryBox"; +import { SearchBox } from "../search/SearchBox"; import { ColorBox } from "./ColorBox"; import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; import { LinkAnchorBox } from "./LinkAnchorBox"; @@ -192,7 +192,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { components={{ FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, MenuIconBox, LabelBox, SliderBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox, - PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, QueryBox, + PDFBox, VideoBox, AudioBox, PresBox, YoutubeBox, PresElementBox, SearchBox, ColorBox, DashWebRTCVideo, LinkAnchorBox, InkingStroke, DocHolderBox, LinkBox, ScriptingBox, ScreenshotBox, HTMLtag, ComparisonBox }} diff --git a/src/client/views/nodes/DocumentLinksButton.scss b/src/client/views/nodes/DocumentLinksButton.scss index 97e714cd5..9328fb96b 100644 --- a/src/client/views/nodes/DocumentLinksButton.scss +++ b/src/client/views/nodes/DocumentLinksButton.scss @@ -28,7 +28,13 @@ } .documentLinksButton { - background-color: $link-color; + background-color: black; + + &:hover { + background: $main-accent; + transform: scale(1.05); + cursor: pointer; + } } .documentLinksButton-endLink { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 025669b41..cb79e1522 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -181,7 +181,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp const linkButton = <div ref={this._linkButton} style={{ minWidth: 20, minHeight: 20, position: "absolute", left: this.props.Offset?.[0] }}> <div className={"documentLinksButton"} style={{ - backgroundColor: this.props.InMenu ? "black" : "", + backgroundColor: this.props.InMenu ? "" : "#add8e6", color: this.props.InMenu ? "white" : "black", width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", fontWeight: "bold" }} @@ -202,8 +202,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp link : links.length} </div> - {DocumentLinksButton.StartLink && this.props.InMenu && !this.props.StartLink && - DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} + {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? + <div className={"documentLinksButton-endLink"} style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px", backgroundColor: DocumentLinksButton.StartLink ? "" : "grey", @@ -218,7 +218,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp </div>; return (!links.length) && !this.props.AlwaysOn ? (null) : - this.props.InMenu ? + this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? <Tooltip title={<><div className="dash-tooltip">{title}</div></>}> {linkButton} </Tooltip> : !!!DocumentLinksButton.EditLink && !this.props.InMenu ? diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e6e709621..b9e685b44 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -749,7 +749,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu moreItems.push({ description: "Download document", icon: "download", event: async () => Doc.Zip(this.props.Document) }); moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this), icon: "users" }); //moreItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); - moreItems.push({ description: "Create an Alias", event: () => this.onCopy(), icon: "copy" }); + //moreItems.push({ description: "Create an Alias", event: () => this.onCopy(), icon: "copy" }); if (!Doc.UserDoc().noviceMode) { moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 1b4151bd8..e631ad5fe 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -49,11 +49,18 @@ export interface FieldViewProps { ignoreAutoHeight?: boolean; PanelWidth: () => number; PanelHeight: () => number; + PanelPosition?: string; + overflow?: boolean; NativeHeight: () => number; NativeWidth: () => number; setVideoBox?: (player: VideoBox) => void; ContentScaling: () => number; + ChromeHeight?: () => number; + childLayoutTemplate?: () => Opt<Doc>; + highlighting?: string[]; + lines?: string[]; + doc?: Doc; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) height?: number; width?: number; diff --git a/src/client/views/nodes/FontIconBox.scss b/src/client/views/nodes/FontIconBox.scss index 5bdafd857..9709e1dbd 100644 --- a/src/client/views/nodes/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox.scss @@ -15,12 +15,17 @@ .menuButton-round { border-radius: 100%; + background-color: black; .fontIconBox-label { margin-left: -10px; // button padding is 10px; bottom: 0; position: absolute; } + + &:hover { + background-color: #aaaaa3; + } } .menuButton-square { diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index eff5a4160..c0eb78d98 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -63,16 +63,14 @@ export class FontIconBox extends DocComponent<FieldViewProps, FontIconDocument>( const color = StrCast(this.layoutDoc.color, this._foregroundColor); const backgroundColor = StrCast(this.layoutDoc._backgroundColor, StrCast(this.rootDoc.backgroundColor, this.props.backgroundColor?.(this.rootDoc))); const shape = StrCast(this.layoutDoc.iconShape, "round"); - const button = <> - <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu} - style={{ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor }}> - <div className="menuButton-wrap"> - {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color} - size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />} - {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>} - </div> - </button> - </>; + const button = <button className={`menuButton-${shape}`} ref={this._ref} onContextMenu={this.specificContextMenu} + style={{ boxShadow: this.layoutDoc.ischecked ? `4px 4px 12px black` : undefined, backgroundColor: this.layoutDoc.iconShape === "square" ? backgroundColor : "" }}> + <div className="menuButton-wrap"> + {<FontAwesomeIcon className={`menuButton-icon-${shape}`} icon={StrCast(this.dataDoc.icon, "user") as any} color={color} + size={this.layoutDoc.iconShape === "square" ? "sm" : "lg"} />} + {!label ? (null) : <div className="fontIconBox-label" style={{ color, backgroundColor }}> {label} </div>} + </div> + </button>; return !this.layoutDoc.toolTip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}> {button} diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 05ba6628c..826ccd340 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -1,4 +1,4 @@ -import { action } from 'mobx'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; @@ -56,16 +56,26 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps, LabelDocument e.stopPropagation(); } } + + @observable _mouseOver = false; + @computed get backColor() { return this.clicked || this._mouseOver ? StrCast(this.layoutDoc.hovercolor) : "unset"; } + + @observable clicked = false; // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); const missingParams = params?.filter(p => !this.paramsDoc[p]); params?.map(p => DocListCast(this.paramsDoc[p])); // bcz: really hacky form of prefetching ... return ( - <div className="labelBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu} + <div className="labelBox-outerDiv" + onClick={action(() => this.clicked = !this.clicked)} + onMouseLeave={action(() => this._mouseOver = false)} + onMouseOver={action(() => this._mouseOver = true)} + ref={this.createDropTarget} onContextMenu={this.specificContextMenu} style={{ boxShadow: this.layoutDoc.opacity ? StrCast(this.layoutDoc.boxShadow) : "" }}> <div className="labelBox-mainButton" style={{ background: StrCast(this.layoutDoc.backgroundColor), + backgroundColor: this.backColor, color: StrCast(this.layoutDoc.color, "inherit"), fontSize: StrCast(this.layoutDoc._fontSize) || "inherit", fontFamily: StrCast(this.layoutDoc._fontFamily) || "inherit", diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index 0fff0b57f..1b6056be6 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -1,41 +1,38 @@ -import React = require("react"); -import { IReactionDisposer } from "mobx"; -import { observer } from "mobx-react"; -import { documentSchema } from "../../../fields/documentSchemas"; -import { Id } from '../../../fields/FieldSymbols'; -import { makeInterface, listSpec } from "../../../fields/Schema"; -import { StrCast, Cast } from "../../../fields/Types"; -import { ViewBoxAnnotatableComponent } from '../DocComponent'; -import { SearchBox } from "../search/SearchBox"; -import { FieldView, FieldViewProps } from './FieldView'; -import "./QueryBox.scss"; -import { List } from "../../../fields/List"; -import { SnappingManager } from "../../util/SnappingManager"; +// import React = require("react"); +// import { IReactionDisposer } from "mobx"; +// import { observer } from "mobx-react"; +// import { documentSchema } from "../../../new_fields/documentSchemas"; +// import { Id } from '../../../new_fields/FieldSymbols'; +// import { makeInterface, listSpec } from "../../../new_fields/Schema"; +// import { StrCast, Cast } from "../../../new_fields/Types"; +// import { ViewBoxAnnotatableComponent } from '../DocComponent'; +// import { SearchBox } from "../search/SearchBox"; +// import { FieldView, FieldViewProps } from './FieldView'; +// import "./QueryBox.scss"; +// import { List } from "../../../new_fields/List"; +// import { SnappingManager } from "../../util/SnappingManager"; -type QueryDocument = makeInterface<[typeof documentSchema]>; -const QueryDocument = makeInterface(documentSchema); +// type QueryDocument = makeInterface<[typeof documentSchema]>; +// const QueryDocument = makeInterface(documentSchema); -@observer -export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } - _docListChangedReaction: IReactionDisposer | undefined; - componentDidMount() { - } +// @observer +// export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) { +// public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } +// _docListChangedReaction: IReactionDisposer | undefined; +// componentDidMount() { +// } - componentWillUnmount() { - this._docListChangedReaction?.(); - } +// componentWillUnmount() { +// this._docListChangedReaction?.(); +// } - render() { - const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging"; - return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} > - <SearchBox - id={this.props.Document[Id]} - setSearchQuery={q => this.dataDoc.searchQuery = q} - searchQuery={StrCast(this.dataDoc.searchQuery)} - setSearchFileTypes={q => this.dataDoc.searchFileTypes = new List<string>(q)} - searchFileTypes={Cast(this.dataDoc.searchFileTypes, listSpec("string"), [])} - filterQquery={StrCast(this.dataDoc.filterQuery)} /> - </div >; - } -}
\ No newline at end of file +// render() { +// const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging"; +// return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} > + +// <SearchBox Document={this.props.Document} /> +// </div >; +// } +// } + +// //<SearchBox id={this.props.Document[Id]} sideBar={side} Document={this.props.Document} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQuery={this.dataDoc.filterQuery} /> diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 646a94aa7..3283f568a 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum highlight = (color: string) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.Document); - const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color); + const annotationDoc = [AclAddonly, AclEdit, AclAdmin].includes(effectiveAcl) ? this.makeAnnotationDocument(color) : undefined; annotationDoc && this.addDocument?.(annotationDoc); return annotationDoc ?? undefined; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ab1de5529..af4bd77c7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -236,7 +236,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { this._applyingChange = true; - (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); + const lastmodified = "lastmodified"; + (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))) && (this.dataDoc[lastmodified] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (json.replace(/"selection":.*/, "") !== curLayout?.Data.replace(/"selection":.*/, "")) { !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize)); @@ -270,7 +271,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.rootDoc.customTitle) { let node = this._editorView.state.doc; - while (node.firstChild) node = node.firstChild; + while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; const titlestr = str.substr(0, Math.min(40, str.length)); this.dataDoc.title = "-" + titlestr + (str.length > 40 ? "..." : ""); @@ -292,18 +293,40 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.dispatch(tr.addMark(flattened[lastSel].from, flattened[lastSel].to, link)); } } - public highlightSearchTerms = (terms: string[]) => { + public highlightSearchTerms = (terms: string[], alt: boolean) => { if (this._editorView && (this._editorView as any).docView && terms.some(t => t)) { + const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const length = res[0].length; let tr = this._editorView.state.tr; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); + + const lastSel = Math.min(flattened.length - 1, this._searchIndex); flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + if (alt === true) { + if (this._searchIndex > 1) { + this._searchIndex += -2; + } + else if (this._searchIndex === 1) { + this._searchIndex = length - 1; + } + else if (this._searchIndex === 0 && length !== 1) { + this._searchIndex = length - 2; + } + + } + else { + + } + const index = this._searchIndex; + + Doc.GetProto(this.dataDoc).searchIndex = index; } } @@ -314,6 +337,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); const end = this._editorView.state.doc.nodeSize - 2; this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + } if (FormattedTextBox.PasteOnLoad) { const pdfDocId = FormattedTextBox.PasteOnLoad.clipboardData?.getData("dash/pdfOrigin"); @@ -799,8 +823,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.setupEditor(this.config, this.props.fieldKey); + this._disposers.searchAlt = reaction(() => this.rootDoc.searchMatchAlt, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], false) : this.unhighlightSearchTerms(), + { fireImmediately: true }); this._disposers.search = reaction(() => this.rootDoc.searchMatch, - search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), + search => search ? this.highlightSearchTerms([Doc.SearchQuery()], true) : this.unhighlightSearchTerms(), { fireImmediately: this.rootDoc.searchMatch ? true : false }); this._disposers.record = reaction(() => this._recording, diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 5af07c15d..459632ec8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -274,7 +274,7 @@ export default class RichTextMenu extends AntimodeMenu { } !activeFamilies.length && (activeFamilies.push(StrCast(this.TextView.layoutDoc._fontFamily, StrCast(Doc.UserDoc().fontFamily)))); !activeSizes.length && (activeSizes.push(StrCast(this.TextView.layoutDoc._fontSize, StrCast(Doc.UserDoc().fontSize)))); - !activeColors.length && (activeSizes.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); + !activeColors.length && (activeColors.push(StrCast(this.TextView.layoutDoc.color, StrCast(Doc.UserDoc().fontColor)))); } !activeFamilies.length && (activeFamilies.push(StrCast(Doc.UserDoc().fontFamily))); !activeSizes.length && (activeSizes.push(StrCast(Doc.UserDoc().fontSize))); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 192a6300a..cfa9a1844 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -106,6 +106,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu private _scrollTopReactionDisposer?: IReactionDisposer; private _filterReactionDisposer?: IReactionDisposer; private _searchReactionDisposer?: IReactionDisposer; + private _searchReactionDisposer2?: IReactionDisposer; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _selectionText: string = ""; @@ -336,6 +337,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu nextAnnotation = () => { this.Index = Math.min(this.Index + 1, this.allAnnotations.length - 1); this.scrollToAnnotation(this.allAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y))[this.Index]); + this.Document.searchIndex = this.Index; + } @action @@ -408,6 +411,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu phraseSearch: true, query: searchString }); + this.Document.searchIndex = this.Index; } else if (this._mainCont.current) { const executeFind = () => { @@ -421,7 +425,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu }; this._mainCont.current.addEventListener("pagesloaded", executeFind); this._mainCont.current.addEventListener("pagerendered", executeFind); + this.Document.searchIndex = this.Index; } + } @action diff --git a/src/client/views/search/FieldFilters.scss b/src/client/views/search/FieldFilters.scss deleted file mode 100644 index e1d0d8df5..000000000 --- a/src/client/views/search/FieldFilters.scss +++ /dev/null @@ -1,12 +0,0 @@ -.field-filters { - width: 100%; - display: grid; - // grid-template-columns: 18% 20% 60%; - grid-template-columns: 20% 25% 60%; -} - -.field-filters-required { - width: 100%; - display: grid; - grid-template-columns: 50% 50%; -}
\ No newline at end of file diff --git a/src/client/views/search/FieldFilters.tsx b/src/client/views/search/FieldFilters.tsx deleted file mode 100644 index 7a33282d2..000000000 --- a/src/client/views/search/FieldFilters.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from 'react'; -import { observable } from 'mobx'; -import { CheckBox } from './CheckBox'; -import { Keys } from './FilterBox'; -import "./FieldFilters.scss"; - -export interface FieldFilterProps { - titleFieldStatus: boolean; - dataFieldStatus: boolean; - authorFieldStatus: boolean; - updateTitleStatus(stat: boolean): void; - updateAuthorStatus(stat: boolean): void; - updateDataStatus(stat: boolean): void; -} - -export class FieldFilters extends React.Component<FieldFilterProps> { - - static Instance: FieldFilters; - - @observable public _resetBoolean = false; - @observable public _resetCounter: number = 0; - - constructor(props: FieldFilterProps) { - super(props); - FieldFilters.Instance = this; - } - - resetFieldFilters() { - this._resetBoolean = true; - } - - render() { - return ( - <div className="field-filters"> - <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.titleFieldStatus} updateStatus={this.props.updateTitleStatus} title={Keys.TITLE} /> - <CheckBox default={true} numCount={3} parent={this} originalStatus={this.props.authorFieldStatus} updateStatus={this.props.updateAuthorStatus} title={Keys.AUTHOR} /> - <CheckBox default={false} numCount={3} parent={this} originalStatus={this.props.dataFieldStatus} updateStatus={this.props.updateDataStatus} title={"Deleted Docs"} /> - </div> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss deleted file mode 100644 index 094ea9cc5..000000000 --- a/src/client/views/search/FilterBox.scss +++ /dev/null @@ -1,178 +0,0 @@ -@import "../globalCssVariables"; -@import "./NaviconButton.scss"; - -.filter-form { - padding: 25px; - width: 440px; - position: relative; - right: 1px; - color: grey; - flex-direction: column; - display: inline-block; - transform-origin: top; - overflow: auto; - border-bottom: solid black 3px; - - .top-filter-header { - - #header { - text-transform: uppercase; - letter-spacing: 2px; - font-size: 13; - width: 80%; - } - - .close-icon { - width: 20%; - opacity: .6; - position: relative; - display: block; - - .line { - display: block; - background: $alt-accent; - width: 20; - height: 3; - position: absolute; - right: 0; - border-radius: ($height-line / 2); - - &.line-1 { - transform: rotate(45deg); - top: 45%; - } - - &.line-2 { - transform: rotate(-45deg); - top: 45%; - } - } - } - - .close-icon:hover { - opacity: 1; - } - - } - - .filter-options { - - .filter-div { - margin-top: 10px; - margin-bottom: 10px; - display: inline-block; - width: 100%; - border-color: rgba(178, 206, 248, .2); // $darker-alt-accent - border-top-style: solid; - - .filter-header { - display: flex; - align-items: center; - margin-bottom: 10px; - letter-spacing: 2px; - - .filter-title { - font-size: 13; - text-transform: uppercase; - margin-top: 10px; - margin-bottom: 10px; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - } - } - - .filter-header:hover .filter-title { - transform: scale(1.05); - } - - .filter-panel { - max-height: 0px; - width: 100%; - overflow: hidden; - opacity: 0; - transform-origin: top; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - text-align: center; - } - } - } - - .filter-buttons { - border-color: rgba(178, 206, 248, .2); // $darker-alt-accent - border-top-style: solid; - padding-top: 10px; - } -} - -.active-filters { - display: flex; - flex-direction: row-reverse; - justify-content: flex-end; - width: 100%; - margin-right: 30px; - position: relative; - - .active-icon { - max-width: 40px; - flex: initial; - - &.icon { - width: 40px; - text-align: center; - margin-bottom: 5px; - position: absolute; - } - - &.container { - display: flex; - flex-direction: column; - width: 40px; - } - - &.description { - text-align: center; - top: 40px; - position: absolute; - width: 40px; - font-size: 9px; - opacity: 0; - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; - } - - &.icon:hover+.description { - opacity: 1; - } - } - - .col-icon { - height: 35px; - margin-left: 5px; - width: 35px; - background-color: black; - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - - .save-filter, - .reset-filter, - .all-filter { - background-color: gray; - } - - .save-filter:hover, - .reset-filter:hover, - .all-filter:hover { - background-color: $darker-alt-accent; - } - } -}
\ No newline at end of file diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx deleted file mode 100644 index eb61f9a14..000000000 --- a/src/client/views/search/FilterBox.tsx +++ /dev/null @@ -1,431 +0,0 @@ -import * as React from 'react'; -import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; -import "./SearchBox.scss"; -import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons'; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { Doc } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { Cast, StrCast } from '../../../fields/Types'; -import * as _ from "lodash"; -import { IconBar } from './IconBar'; -import { FieldFilters } from './FieldFilters'; -import { SelectionManager } from '../../util/SelectionManager'; -import { DocumentView } from '../nodes/DocumentView'; -import { CollectionFilters } from './CollectionFilters'; -import * as $ from 'jquery'; -import "./FilterBox.scss"; -import { SearchBox } from './SearchBox'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -library.add(faTimes); -library.add(faCheckCircle); -library.add(faObjectGroup); - -export enum Keys { - TITLE = "title", - AUTHOR = "author", - DATA = "data" -} - -@observer -export class FilterBox extends React.Component { - - static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.RTF, DocumentType.VID, DocumentType.WEB]; - - //if true, any keywords can be used. if false, all keywords are required. - //this also serves as an indicator if the word status filter is applied - @observable private _basicWordStatus: boolean = true; - @observable private _filterOpen: boolean = false; - //if icons = all icons, then no icon filter is applied - @observable private _icons: string[] = this._allIcons; - //if all of these are true, no key filter is applied - @observable private _anyKeywordStatus: boolean = true; - @observable private _allKeywordStatus: boolean = true; - @observable private _titleFieldStatus: boolean = true; - @observable private _authorFieldStatus: boolean = true; - @observable private _dataFieldStatus: boolean = true; - //this also serves as an indicator if the collection status filter is applied - @observable public _deletedDocsStatus: boolean = false; - @observable private _collectionStatus = false; - @observable private _collectionSelfStatus = true; - @observable private _collectionParentStatus = true; - @observable private _wordStatusOpen: boolean = false; - @observable private _typeOpen: boolean = false; - @observable private _colOpen: boolean = false; - @observable private _fieldOpen: boolean = false; - public _pointerTime: number = -1; - - constructor(props: Readonly<{}>) { - super(props); - FilterBox.Instance = this; - } - setupAccordion() { - $('document').ready(function () { - const acc = document.getElementsByClassName('filter-header'); - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < acc.length; i++) { - acc[i].addEventListener("click", function (this: HTMLElement) { - this.classList.toggle("active"); - - const panel = this.nextElementSibling as HTMLElement; - if (panel.style.maxHeight) { - panel.style.overflow = "hidden"; - panel.style.maxHeight = ""; - panel.style.opacity = "0"; - } else { - setTimeout(() => { - panel.style.overflow = "visible"; - }, 200); - setTimeout(() => { - panel.style.opacity = "1"; - }, 50); - panel.style.maxHeight = panel.scrollHeight + "px"; - - } - }); - - const el = acc[i] as HTMLElement; - el.click(); - } - }); - } - - @action.bound - minimizeAll() { - $('document').ready(function () { - const acc = document.getElementsByClassName('filter-header'); - - // tslint:disable-next-line: prefer-for-of - for (var i = 0; i < acc.length; i++) { - const classList = acc[i].classList; - if (classList.contains("active")) { - acc[i].classList.toggle("active"); - const panel = acc[i].nextElementSibling as HTMLElement; - panel.style.overflow = "hidden"; - panel.style.maxHeight = ""; - } - } - }); - } - - @action.bound - resetFilters = () => { - this._basicWordStatus = true; - IconBar.Instance.selectAll(); - FieldFilters.Instance.resetFieldFilters(); - } - - basicRequireWords(query: string): string { - const oldWords = query.split(" "); - const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = "+" + word; - newWords.push(newWrd); - }); - query = newWords.join(" "); - - return query; - } - - basicFieldFilters(query: string, type: string): string { - const oldWords = query.split(" "); - let mod = ""; - - if (type === Keys.AUTHOR) { - mod = " author_t:"; - } if (type === Keys.DATA) { - //TODO - } if (type === Keys.TITLE) { - mod = " title_t:"; - } - - const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = mod + word; - newWords.push(newWrd); - }); - - query = newWords.join(" "); - - return query; - } - - applyBasicFieldFilters(query: string) { - let finalQuery = ""; - - if (this._titleFieldStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TITLE); - } - if (this._authorFieldStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.AUTHOR); - } - if (this._deletedDocsStatus) { - finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); - } - return finalQuery; - } - - get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } - - //TODO: basically all of this - //gets all of the collections of all the docviews that are selected - //if a collection is the only thing selected, search only in that collection (not its container) - getCurCollections(): Doc[] { - const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments(); - const collections: Doc[] = []; - - selectedDocs.forEach(async element => { - const layout: string = StrCast(element.props.Document.layout); - //checks if selected view (element) is a collection. if it is, adds to list to search through - if (layout.indexOf("Collection") > -1) { - //makes sure collections aren't added more than once - if (!collections.includes(element.props.Document)) { - collections.push(element.props.Document); - } - } - //makes sure collections aren't added more than once - if (element.props.ContainingCollectionDoc && !collections.includes(element.props.ContainingCollectionDoc)) { - collections.push(element.props.ContainingCollectionDoc); - } - }); - - return collections; - } - - getFinalQuery(query: string): string { - //alters the query so it looks in the correct fields - //if this is true, then not all of the field boxes are checked - //TODO: data - if (this.fieldFiltersApplied) { - query = this.applyBasicFieldFilters(query); - query = query.replace(/\s+/g, ' ').trim(); - } - - //alters the query based on if all words or any words are required - //if this._wordstatus is false, all words are required and a + is added before each - if (!this._basicWordStatus) { - query = this.basicRequireWords(query); - query = query.replace(/\s+/g, ' ').trim(); - } - - //if should be searched in a specific collection - if (this._collectionStatus) { - query = this.addCollectionFilter(query); - query = query.replace(/\s+/g, ' ').trim(); - } - return query; - } - - addCollectionFilter(query: string): string { - const collections: Doc[] = this.getCurCollections(); - const oldWords = query.split(" "); - - const collectionString: string[] = []; - collections.forEach(doc => { - const proto = doc.proto; - const protoId = (proto || doc)[Id]; - const colString: string = "{!join from=data_l to=id}id:" + protoId + " "; - collectionString.push(colString); - }); - - let finalColString = collectionString.join(" "); - finalColString = finalColString.trim(); - return "+(" + finalColString + ")" + query; - } - - get filterTypes() { - return this._icons.length === 9 ? undefined : this._icons; - } - - @action - filterDocsByType(docs: Doc[]) { - if (this._icons.length === 9) { - return docs; - } - const finalDocs: Doc[] = []; - docs.forEach(doc => { - const layoutresult = Cast(doc.type, "string"); - if (layoutresult && this._icons.includes(layoutresult)) { - finalDocs.push(doc); - } - }); - return finalDocs; - } - - getABCicon() { - return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> - <path d="M25.4 47.9c-1.3 1.3-1.9 2.8-1.9 4.8 0 3.8 2.3 6.1 6.1 6.1 5.1 0 8-3.3 9-6.2 0.2-0.7 0.4-1.4 0.4-2.1v-6.1c-0.1 0-0.1 0-0.2 0C32.2 44.5 27.7 45.6 25.4 47.9z" /> - <path d="M64.5 28.6c-2.2 0-4.1 1.5-4.7 3.8l0 0.2c-0.1 0.3-0.1 0.7-0.1 1.1v3.3c0 0.4 0.1 0.8 0.2 1.1 0.6 2.2 2.4 3.6 4.6 3.6 3.2 0 5.2-2.6 5.2-6.7C69.5 31.8 68 28.6 64.5 28.6z" /> - <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM40.1 65.5l-0.5-4c-3 3.1-7.4 4.9-12.1 4.9 -6.8 0-13.6-4.4-13.6-12.8 0-4 1.3-7.4 4-10 4.1-4.1 11.1-6.2 20.8-6.3 0-5.5-2.9-8.4-8.3-8.4 -3.6 0-7.4 1.1-10.2 2.9l-1.1 0.7 -2.4-6.9 0.7-0.4c3.7-2.4 8.9-3.8 14.1-3.8 10.9 0 16.7 6.2 16.7 17.9V54.6c0 4.1 0.2 7.2 0.7 9.7L49 65.5H40.1zM65.5 67.5c1.8 0 3-0.5 4-0.9l0.5-0.2 0.8 3.4 -0.3 0.2c-1 0.5-3 1.1-5.5 1.1 -5.8 0-9.7-4-9.7-9.9 0-6.1 4.3-10.3 10.4-10.3 2.1 0 4 0.5 4.9 1l0.3 0.2 -1 3.5 -0.5-0.3c-0.7-0.4-1.8-0.8-3.7-0.8 -3.7 0-6.1 2.6-6.1 6.6C59.5 64.8 61.9 67.5 65.5 67.5zM65 45.3c-2.5 0-4.5-0.9-5.9-2.7l-0.1 2.3h-3.8l0-0.5c0.1-1.2 0.2-3.1 0.2-4.8V16.7h4.3v10.8c1.4-1.6 3.5-2.5 6-2.5 2.2 0 4.1 0.8 5.5 2.3 1.8 1.8 2.8 4.5 2.8 7.7C73.8 42.1 69.3 45.3 65 45.3z" /> - </svg> - ); - } - - getTypeIcon() { - return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> - <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM43.9 12.2c4.1 0 7.5 3.4 7.5 7.5 0 4.1-3.4 7.5-7.5 7.5 -4.1 0-7.5-3.4-7.5-7.5C36.4 15.5 39.7 12.2 43.9 12.2zM11.9 50.4l7.5-13 7.5 13H11.9zM47.6 75.7h-7.5l-3.7-6.5 3.8-6.5h7.5l3.8 6.5L47.6 75.7zM70.7 70.7c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3l-25.4-25.4 -25.4 25.4c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1 0-1.4l25.4-25.4 -25.4-25.4c-0.4-0.4-0.4-1 0-1.4s1-0.4 1.4 0l25.4 25.4 25.4-25.4c0.4-0.4 1-0.4 1.4 0s0.4 1 0 1.4l-25.4 25.4 25.4 25.4C71.1 69.7 71.1 70.3 70.7 70.7zM61.4 51.4v-15h15v15H61.4z" /> - </svg> - ); - } - - getKeyIcon() { - return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35"> - <path d="M38.5 32.4c0 3.4-2.7 6.1-6.1 6.1 -3.4 0-6.1-2.7-6.1-6.1 0-3.4 2.8-6.1 6.1-6.1C35.8 26.3 38.5 29 38.5 32.4zM87.8 43.9c0 24.2-19.6 43.9-43.9 43.9S0 68.1 0 43.9C0 19.7 19.7 0 43.9 0S87.8 19.7 87.8 43.9zM66.8 60.3L50.2 43.7c-0.5-0.5-0.6-1.2-0.4-1.8 2.4-5.6 1.1-12.1-3.2-16.5 -5.9-5.8-15.4-5.8-21.2 0l0 0c-4.3 4.3-5.6 10.8-3.2 16.5 3.2 7.6 12 11.2 19.7 8 0.6-0.3 1.4-0.1 1.8 0.4l3.1 3.1h3.9c1.2 0 2.2 1 2.2 2.2v3.6h3.6c1.2 0 2.2 1 2.2 2.2v4l1.6 1.6h6.5V60.3z" /> - </svg> - ); - } - - getColIcon() { - return ( - <div className="col-icon"> - <FontAwesomeIcon icon={faObjectGroup} size="lg" /> - </div> - ); - } - - @action.bound - openFilter = () => { - this._filterOpen = !this._filterOpen; - SearchBox.Instance.closeResults(); - this.setupAccordion(); - } - - //if true, any keywords can be used. if false, all keywords are required. - @action.bound - handleWordQueryChange = () => { - this._basicWordStatus = !this._basicWordStatus; - } - - @action.bound - updateIcon(newArray: string[]) { this._icons = newArray; } - - @action.bound - getIcons(): string[] { return this._icons; } - - stopProp = (e: React.PointerEvent) => { - e.stopPropagation(); - this._pointerTime = e.timeStamp; - } - - @action.bound - public closeFilter() { - this._filterOpen = false; - } - - @action.bound - updateAnyKeywordStatus(newStat: boolean) { this._anyKeywordStatus = newStat; } - - @action.bound - updateAllKeywordStatus(newStat: boolean) { this._allKeywordStatus = newStat; } - - @action.bound - updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; } - - @action.bound - updateAuthorStatus(newStat: boolean) { this._authorFieldStatus = newStat; } - - @action.bound - updateDataStatus(newStat: boolean) { this._deletedDocsStatus = newStat; } - - @action.bound - updateCollectionStatus(newStat: boolean) { this._collectionStatus = newStat; } - - @action.bound - updateSelfCollectionStatus(newStat: boolean) { this._collectionSelfStatus = newStat; } - - @action.bound - updateParentCollectionStatus(newStat: boolean) { this._collectionParentStatus = newStat; } - - getAnyKeywordStatus() { return this._anyKeywordStatus; } - getAllKeywordStatus() { return this._allKeywordStatus; } - getCollectionStatus() { return this._collectionStatus; } - getSelfCollectionStatus() { return this._collectionSelfStatus; } - getParentCollectionStatus() { return this._collectionParentStatus; } - getTitleStatus() { return this._titleFieldStatus; } - getAuthorStatus() { return this._authorFieldStatus; } - getDataStatus() { return this._deletedDocsStatus; } - - getActiveFilters() { - return ( - <div className="active-filters"> - {!this._basicWordStatus ? <div className="active-icon container"> - <div className="active-icon icon">{this.getABCicon()}</div> - <div className="active-icon description">Required Words Applied</div> - </div> : undefined} - {!(this._icons.length === 9) ? <div className="active-icon container"> - <div className="active-icon icon">{this.getTypeIcon()}</div> - <div className="active-icon description">Type Filters Applied</div> - </div> : undefined} - {!(this._authorFieldStatus && this._dataFieldStatus && this._titleFieldStatus) ? - <div className="active-icon container"> - <div className="active-icon icon">{this.getKeyIcon()}</div> - <div className="active-icon description">Field Filters Applied</div> - </div> : undefined} - {this._collectionStatus ? <div className="active-icon container"> - <div className="active-icon icon">{this.getColIcon()}</div> - <div className="active-icon description">Collection Filters Active</div> - </div> : undefined} - </div> - ); - } - - // Useful queries: - // Delegates of a document: {!join from=id to=proto_i}id:{protoId} - // Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId} //id of collections prototype - render() { - return ( - <div> - <div style={{ display: "flex", flexDirection: "row-reverse" }}> - <SearchBox /> - {/* {this.getActiveFilters()} */} - </div> - {this._filterOpen ? ( - <div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex", background: "black" } : { display: "none" }}> - <div className="top-filter-header" style={{ display: "flex", width: "100%" }}> - <div id="header">Filter Search Results</div> - <div style={{ marginLeft: "auto" }}></div> - <div className="close-icon" onClick={this.closeFilter}> - <span className="line line-1"></span> - <span className="line line-2"></span></div> - </div> - <div className="filter-options"> - <div className="filter-div"> - <div className="filter-header"> - <div className='filter-title words'>Required words</div> - </div> - <div className="filter-panel" > - <button className="all-filter" onClick={this.handleWordQueryChange}>Include All Keywords</button> - </div> - </div> - <div className="filter-div"> - <div className="filter-header"> - <div className="filter-title icon">Filter by type of node</div> - </div> - <div className="filter-panel"><IconBar /></div> - </div> - <div className="filter-div"> - <div className="filter-header"> - <div className="filter-title field">Filter by Basic Keys</div> - </div> - <div className="filter-panel"><FieldFilters - titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus} - updateAuthorStatus={this.updateAuthorStatus} updateDataStatus={this.updateDataStatus} updateTitleStatus={this.updateTitleStatus} /> </div> - </div> - </div> - <div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}> - <button className="save-filter" >Save Filters</button> - <button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button> - </div> - </div> - ) : - undefined} - </div> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss index bb62113a1..3f06ba7d3 100644 --- a/src/client/views/search/SearchBox.scss +++ b/src/client/views/search/SearchBox.scss @@ -17,10 +17,9 @@ .searchBox-bar { height: 32px; display: flex; - justify-content: flex-end; + justify-content: center; align-items: center; - padding-left: 2px; - + background-color: black; .searchBox-barChild { &.searchBox-collection { @@ -30,24 +29,29 @@ } &.searchBox-input { + margin:5px; + border-radius:20px; + border:black; display: block; width: 130px; -webkit-transition: width 0.4s; transition: width 0.4s; align-self: stretch; - + outline:none; } .searchBox-input:focus { width: 500px; - outline: 3px solid lightblue; + outline:none; } &.searchBox-filter { align-self: stretch; + button{ + transform:none; + } button:hover{ - transform:scale(1.0); - background:"#121721"; + transform:none; } } @@ -81,8 +85,6 @@ .no-result { width: 500px; background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; padding: 10px; height: 50px; text-transform: uppercase; @@ -96,20 +98,20 @@ background: #121721; flex-direction: column; transform-origin: top; - transition: height 0.3s ease, display 0.6s ease; + transition: height 0.3s ease, display 0.6s ease, overflow 0.6s ease; height:0px; overflow:hidden; .filter-header { - display: flex; + //display: flex; position: relative; - flex-wrap:wrap; + //flex-wrap:wrap; right: 1px; color: grey; - flex-direction: row-reverse; + //flex-direction: row-reverse; transform-origin: top; - justify-content: space-evenly; + //justify-content: space-evenly; margin-bottom: 5px; overflow:hidden; transition:height 0.3s ease-out; @@ -130,9 +132,7 @@ color: grey; transform-origin: top; border-top: 0px; - //padding-top: 5px; - margin-left: 10px; - margin-right: 10px; + overflow:hidden; transition:height 0.3s ease-out; height:0px; @@ -144,30 +144,25 @@ color: grey; transform-origin: top; border-top: 0px; - //padding-top: 5px; - margin-left: 10px; - margin-right: 10px; overflow:hidden; transition:height 0.3s ease-out; height:0px; - .filter-keybar { - display: flex; - flex-wrap: wrap; - justify-content: space-evenly; - height: auto; - width: 100%; - flex-direction: row-reverse; - margin-top:5px; + + // .filter-keybar { + // display: flex; + // flex-wrap: wrap; + // justify-content: space-evenly; + // height: auto; + // width: 100%; + // flex-direction: row-reverse; + // margin-top:5px; - .filter-item { - position: relative; - border:1px solid grey; - border-radius: 16px; - - } - } - - + // .filter-item { + // position: relative; + // border:1px solid grey; + // border-radius: 16px; + // } + // } } } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 99fa6da21..1e44a379b 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -1,59 +1,66 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, IReactionDisposer, reaction } from 'mobx'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Tooltip } from '@material-ui/core'; +import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as rp from 'request-promise'; -import { Doc } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { Utils } from '../../../Utils'; +import { returnFalse, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { SetupDrag } from '../../util/DragManager'; import { SearchUtil } from '../../util/SearchUtil'; -import "./SearchBox.scss"; -import { SearchItem } from './SearchItem'; -import { IconBar } from './IconBar'; -import { FieldView } from '../nodes/FieldView'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentView } from '../nodes/DocumentView'; import { SelectionManager } from '../../util/SelectionManager'; -import { listSpec } from '../../../fields/Schema'; - -library.add(faTimes); +import { Transform } from '../../util/Transform'; +import { CollectionView, CollectionViewType } from '../collections/CollectionView'; +import { ViewBoxBaseComponent } from "../DocComponent"; +import { DocumentView } from '../nodes/DocumentView'; +import { FieldView, FieldViewProps } from '../nodes/FieldView'; +import "./SearchBox.scss"; -export interface SearchProps { - id: string; - searchQuery: string; - filterQquery?: string; - setSearchQuery: (q: string) => {}; - searchFileTypes: string[]; - setSearchFileTypes: (types: string[]) => {}; -} +export const searchSchema = createSchema({ + id: "string", + Document: Doc, + searchQuery: "string", +}); export enum Keys { TITLE = "title", AUTHOR = "author", - DATA = "data" + DATA = "data", + TEXT = "text" } +type SearchBoxDocument = makeInterface<[typeof documentSchema, typeof searchSchema]>; +const SearchBoxDocument = makeInterface(documentSchema, searchSchema); + +//React.Component<SearchProps> @observer -export class SearchBox extends React.Component<SearchProps> { +export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDocument>(SearchBoxDocument) { - private get _searchString() { return this.props.searchQuery; } - private set _searchString(value) { this.props.setSearchQuery(value); } + get _searchString() { return this.layoutDoc.searchQuery; } + @computed set _searchString(value) { this.layoutDoc.searchQuery = (value); } @observable private _resultsOpen: boolean = false; - @observable private _searchbarOpen: boolean = false; + @observable _searchbarOpen: boolean = false; @observable private _results: [Doc, string[], string[]][] = []; @observable private _openNoResults: boolean = false; @observable private _visibleElements: JSX.Element[] = []; + @observable private _visibleDocuments: Doc[] = []; private _resultsSet = new Map<Doc, number>(); private _resultsRef = React.createRef<HTMLDivElement>(); public inputRef = React.createRef<HTMLInputElement>(); private _isSearch: ("search" | "placeholder" | undefined)[] = []; + private _isSorted: ("sorted" | "placeholder" | undefined)[] = []; + private _numTotalResults = -1; private _endIndex = -1; @@ -63,55 +70,104 @@ export class SearchBox extends React.Component<SearchProps> { private _curRequest?: Promise<any> = undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SearchBox, fieldKey); } - + private new_buckets: { [characterName: string]: number } = {}; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied @observable private _basicWordStatus: boolean = false; @observable private _nodeStatus: boolean = false; @observable private _keyStatus: boolean = false; + @observable private newAssign: boolean = true; constructor(props: any) { super(props); SearchBox.Instance = this; this.resultsScrolled = this.resultsScrolled.bind(this); + } + @observable setupButtons = false; + componentDidMount = () => { + if (this.setupButtons === false) { - componentDidMount = action(() => { + runInAction(() => this.setupButtons = true); + } if (this.inputRef.current) { this.inputRef.current.focus(); - this._searchbarOpen = true; + runInAction(() => { this._searchbarOpen = true; }); } - if (this.props.searchQuery) { // bcz: why was this here? } && this.props.filterQquery) { - this._searchString = this.props.searchQuery; - this.submitSearch(); + if (this.rootDoc.searchQuery && this.newAssign) { + const sq = this.rootDoc.searchQuery; + runInAction(() => { + + // this._deletedDocsStatus=this.props.filterQuery!.deletedDocsStatus; + // this._authorFieldStatus=this.props.filterQuery!.authorFieldStatus + // this._titleFieldStatus=this.props.filterQuery!.titleFieldStatus; + // this._basicWordStatus=this.props.filterQuery!.basicWordStatus; + // this._icons=this.props.filterQuery!.icons; + this.newAssign = false; + }); + runInAction(() => { + this.layoutDoc._searchString = StrCast(sq); + this.submitSearch(); + }); } - }); + } @action getViews = (doc: Doc) => SearchUtil.GetViewsOfDocument(doc) + + @observable newsearchstring: string = ""; @action.bound onChange(e: React.ChangeEvent<HTMLInputElement>) { - this._searchString = e.target.value; + this.layoutDoc._searchString = e.target.value; + this.newsearchstring = e.target.value; - this._openNoResults = false; - this._results = []; - this._resultsSet.clear(); - this._visibleElements = []; - this._numTotalResults = -1; - this._endIndex = -1; - this._curRequest = undefined; - this._maxSearchIndex = 0; + + if (e.target.value === "") { + this._results.forEach(result => { + Doc.UnBrushDoc(result[0]); + result[0].searchMatch = undefined; + }); + + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); + if (this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection = undefined; + this.props.Document.selectedDoc = undefined; + + } + runInAction(() => { this.open = false; }); + this._openNoResults = false; + this._results = []; + this._resultsSet.clear(); + this._visibleElements = []; + this._numTotalResults = -1; + this._endIndex = -1; + this._curRequest = undefined; + this._maxSearchIndex = 0; + } } enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { + this.layoutDoc._searchString = this.newsearchstring; + + if (StrCast(this.layoutDoc._searchString) !== "" || !this.searchFullDB) { + runInAction(() => this.open = true); + } + else { + runInAction(() => this.open = false); + + } this.submitSearch(); } } + @observable open: boolean = false; + + public static async convertDataUri(imageUri: string, returnedFilename: string) { try { const posting = Utils.prepend("/uploadURI"); @@ -134,10 +190,11 @@ export class SearchBox extends React.Component<SearchProps> { //this also serves as an indicator if the word status filter is applied @observable private _filterOpen: boolean = false; //if icons = all icons, then no icon filter is applied - get _icons() { return this.props.searchFileTypes; } - set _icons(value) { - this.props.setSearchFileTypes(value); - } + // get _icons() { return this.props.searchFileTypes; } + // set _icons(value) { + // this.props.setSearchFileTypes(value); + // } + @observable _icons: string[] = this._allIcons; //if all of these are true, no key filter is applied @observable private _titleFieldStatus: boolean = true; @observable private _authorFieldStatus: boolean = true; @@ -162,10 +219,11 @@ export class SearchBox extends React.Component<SearchProps> { query = query.replace(/\s+/g, ' ').trim(); } - //if should be searched in a specific collection + // if should be searched in a specific collection if (this._collectionStatus) { query = this.addCollectionFilter(query); query = query.replace(/\s+/g, ' ').trim(); + } return query; } @@ -176,14 +234,14 @@ export class SearchBox extends React.Component<SearchProps> { @action filterDocsByType(docs: Doc[]) { - if (this._icons.length === this._allIcons.length) { - return docs; - } const finalDocs: Doc[] = []; + const blockedTypes: string[] = ["preselement", "docholder", "collection", "search", "searchitem", "script", "fonticonbox", "button", "label"]; docs.forEach(doc => { const layoutresult = Cast(doc.type, "string"); - if (layoutresult && this._icons.includes(layoutresult)) { - finalDocs.push(doc); + if (layoutresult && !blockedTypes.includes(layoutresult)) { + if (layoutresult && this._icons.includes(layoutresult)) { + finalDocs.push(doc); + } } }); return finalDocs; @@ -216,7 +274,6 @@ export class SearchBox extends React.Component<SearchProps> { getCurCollections(): Doc[] { const selectedDocs: DocumentView[] = SelectionManager.SelectedDocuments(); const collections: Doc[] = []; - selectedDocs.forEach(async element => { const layout: string = StrCast(element.props.Document.layout); //checks if selected view (element) is a collection. if it is, adds to list to search through @@ -236,6 +293,85 @@ export class SearchBox extends React.Component<SearchProps> { } + currentSelectedCollection: DocumentView | undefined = undefined; + docsforfilter: Doc[] = []; + + searchCollection(query: string) { + const selectedCollection: DocumentView = SelectionManager.SelectedDocuments()[0]; + query = query.toLowerCase(); + if (selectedCollection !== undefined) { + this.currentSelectedCollection = selectedCollection; + if (this.filter === true) { + this.props.Document.selectedDoc = selectedCollection.props.Document; + } + let docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + const found: [Doc, string[], string[]][] = []; + const docsforFilter: Doc[] = []; + let newarray: Doc[] = []; + + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + newarray.push(...DocListCast(d.data)); + } + const hlights: string[] = []; + const protos = Doc.GetAllPrototypes(d); + protos.forEach(proto => { + Object.keys(proto).forEach(key => { + if (StrCast(d[key]).toLowerCase().includes(query) && !hlights.includes(key)) { + hlights.push(key); + } + }); + }); + if (hlights.length > 0) { + found.push([d, hlights, []]); + docsforFilter.push(d); + } + }); + docs = newarray; + } + this._results = found; + this.docsforfilter = docsforFilter; + if (this.filter === true) { + selectedCollection.props.Document._searchDocs = new List<Doc>(docsforFilter); + docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(docsforFilter); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + } + this._numTotalResults = found.length; + } + else { + this.noresults = "No collection selected :("; + } + + } + + + documentKeys(doc: Doc) { + const keys: { [key: string]: boolean } = {}; + // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. + // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be + // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. + // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu + // is displayed (unlikely) it won't show up until something else changes. + //TODO Types + Doc.GetAllPrototypes(doc).map + (proto => Object.keys(proto).forEach(key => keys[key] = false)); + return Array.from(Object.keys(keys)); + } + applyBasicFieldFilters(query: string) { let finalQuery = ""; @@ -248,26 +384,24 @@ export class SearchBox extends React.Component<SearchProps> { if (this._deletedDocsStatus) { finalQuery = finalQuery + this.basicFieldFilters(query, Keys.DATA); } + if (this._deletedDocsStatus) { + finalQuery = finalQuery + this.basicFieldFilters(query, Keys.TEXT); + } return finalQuery; } basicFieldFilters(query: string, type: string): string { - const oldWords = query.split(" "); let mod = ""; - - if (type === Keys.AUTHOR) { - mod = " author_t:"; - } if (type === Keys.DATA) { - //TODO - } if (type === Keys.TITLE) { - mod = " title_t:"; + switch (type) { + case Keys.AUTHOR: mod = " author_t:"; break; + case Keys.DATA: break; // TODO + case Keys.TITLE: mod = " _title_t:"; break; + case Keys.TEXT: mod = " text_t:"; break; } const newWords: string[] = []; - oldWords.forEach(word => { - const newWrd = mod + word; - newWords.push(newWrd); - }); + const oldWords = query.split(" "); + oldWords.forEach(word => newWords.push(mod + word)); query = newWords.join(" "); @@ -276,30 +410,64 @@ export class SearchBox extends React.Component<SearchProps> { get fieldFiltersApplied() { return !(this._authorFieldStatus && this._titleFieldStatus); } - @action - submitSearch = async () => { - const query = this._searchString; + submitSearch = async (reset?: boolean) => { + if (reset) { + this.layoutDoc._searchString = ""; + } + this.props.Document._docFilters = undefined; + this.noresults = ""; + + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.headercount = 0; + this.children = 0; + this.buckets = []; + this.new_buckets = {}; + const query = StrCast(this.layoutDoc._searchString); + Doc.SetSearchQuery(query); this.getFinalQuery(query); + this._results.forEach(result => { + Doc.UnBrushDoc(result[0]); + result[0].searchMatch = undefined; + }); this._results = []; this._resultsSet.clear(); this._isSearch = []; + this._isSorted = []; this._visibleElements = []; + this._visibleDocuments = []; + if (StrCast(this.props.Document.searchQuery)) { + if (this._timeout) { clearTimeout(this._timeout); this._timeout = undefined; } + this._timeout = setTimeout(() => { + console.log("Resubmitting search"); + }, 60000); + } + if (query !== "") { this._endIndex = 12; this._maxSearchIndex = 0; this._numTotalResults = -1; - await this.getResults(query); - + this.searchFullDB ? await this.getResults(query) : this.searchCollection(query); runInAction(() => { this._resultsOpen = true; this._searchbarOpen = true; this._openNoResults = true; this.resultsScrolled(); + }); } } + @observable searchFullDB = true; + + @observable _timeout: any = undefined; + + @observable firststring: string = ""; + @observable secondstring: string = ""; + + @observable bucketcount: number[] = []; + @observable buckets: Doc[] | undefined; + getAllResults = async (query: string) => { return SearchUtil.Search(query, true, { fq: this.filterQuery, start: 0, rows: 10000000 }); } @@ -309,7 +477,7 @@ export class SearchBox extends React.Component<SearchProps> { const baseExpr = "NOT baseProto_b:true"; const includeDeleted = this.getDataStatus() ? "" : " NOT deleted_b:true"; const includeIcons = this.getDataStatus() ? "" : " NOT type_t:fonticonbox"; - // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // this line was causing issues for me, check solr logging -syip2 + // const typeExpr = !types ? "" : ` (${types.map(type => `({!join from=id to=proto_i}type_t:"${type}" AND NOT type_t:*) OR type_t:"${type}"`).join(" ")})`; // fq: type_t:collection OR {!join from=id to=proto_i}type_t:collection q:text_t:hello const query = [baseExpr, includeDeleted, includeIcons].join(" AND ").replace(/AND $/, ""); return query; @@ -317,21 +485,20 @@ export class SearchBox extends React.Component<SearchProps> { getDataStatus() { return this._deletedDocsStatus; } - private NumResults = 25; private lockPromise?: Promise<void>; getResults = async (query: string) => { + console.log("Get"); if (this.lockPromise) { await this.lockPromise; } this.lockPromise = new Promise(async res => { while (this._results.length <= this._endIndex && (this._numTotalResults === -1 || this._maxSearchIndex < this._numTotalResults)) { - this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*" }).then(action(async (res: SearchUtil.DocSearchResult) => { + this._curRequest = SearchUtil.Search(query, true, { fq: this.filterQuery, start: this._maxSearchIndex, rows: this.NumResults, hl: true, "hl.fl": "*", }).then(action(async (res: SearchUtil.DocSearchResult) => { // happens at the beginning if (res.numFound !== this._numTotalResults && this._numTotalResults === -1) { this._numTotalResults = res.numFound; } - const highlighting = res.highlighting || {}; const highlightList = res.docs.map(doc => highlighting[doc[Id]]); const lines = new Map<string, string[]>(); @@ -340,19 +507,33 @@ export class SearchBox extends React.Component<SearchProps> { const highlights: typeof res.highlighting = {}; docs.forEach((doc, index) => highlights[doc[Id]] = highlightList[index]); const filteredDocs = this.filterDocsByType(docs); + runInAction(() => { - //this._results.push(...filteredDocs); - filteredDocs.forEach(doc => { + filteredDocs.forEach((doc, i) => { const index = this._resultsSet.get(doc); const highlight = highlights[doc[Id]]; const line = lines.get(doc[Id]) || []; const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []; - if (index === undefined) { - this._resultsSet.set(doc, this._results.length); - this._results.push([doc, hlights, line]); - } else { - this._results[index][1].push(...hlights); - this._results[index][2].push(...line); + doc ? console.log(Cast(doc.context, Doc)) : null; + if (this.findCommonElements(hlights)) { + } + else { + const layoutresult = Cast(doc.type, "string"); + if (layoutresult) { + if (this.new_buckets[layoutresult] === undefined) { + this.new_buckets[layoutresult] = 1; + } + else { + this.new_buckets[layoutresult] = this.new_buckets[layoutresult] + 1; + } + } + if (index === undefined) { + this._resultsSet.set(doc, this._results.length); + this._results.push([doc, hlights, line]); + } else { + this._results[index][1].push(...hlights); + this._results[index][2].push(...line); + } } }); }); @@ -363,15 +544,16 @@ export class SearchBox extends React.Component<SearchProps> { await this._curRequest; } + this.resultsScrolled(); res(); }); return this.lockPromise; } - + @observable noresults = ""; collectionRef = React.createRef<HTMLSpanElement>(); startDragCollection = async () => { - const res = await this.getAllResults(this.getFinalQuery(this._searchString)); + const res = await this.getAllResults(this.getFinalQuery(StrCast(this.layoutDoc._searchString))); const filtered = this.filterDocsByType(res.docs); const docs = filtered.map(doc => { const isProto = Doc.GetT(doc, "isPrototype", "boolean", true); @@ -404,7 +586,7 @@ export class SearchBox extends React.Component<SearchProps> { y += 300; } } - return Docs.Create.QueryDocument({ _autoHeight: true, title: this._searchString, filterQuery: this.filterQuery, searchQuery: this._searchString }); + return Docs.Create.SearchDocument({ _autoHeight: true, _viewType: CollectionViewType.Schema, title: StrCast(this.layoutDoc._searchString), searchQuery: StrCast(this.layoutDoc._searchString) }); } @action.bound @@ -417,7 +599,7 @@ export class SearchBox extends React.Component<SearchProps> { @action.bound closeSearch = () => { - this.closeResults(); + //this.closeResults(); this._searchbarOpen = false; } @@ -427,23 +609,30 @@ export class SearchBox extends React.Component<SearchProps> { this._results = []; this._resultsSet.clear(); this._visibleElements = []; + this._visibleDocuments = []; this._numTotalResults = -1; this._endIndex = -1; this._curRequest = undefined; } + @observable children: number = 0; @action resultsScrolled = (e?: React.UIEvent<HTMLDivElement>) => { if (!this._resultsRef.current) return; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([]); + const scrollY = e ? e.currentTarget.scrollTop : this._resultsRef.current ? this._resultsRef.current.scrollTop : 0; const itemHght = 53; const startIndex = Math.floor(Math.max(0, scrollY / itemHght)); - const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght))); - - this._endIndex = endIndex === -1 ? 12 : endIndex; - + //const endIndex = Math.ceil(Math.min(this._numTotalResults - 1, startIndex + (this._resultsRef.current.getBoundingClientRect().height / itemHght))); + const endIndex = 30; + //this._endIndex = endIndex === -1 ? 12 : endIndex; + this._endIndex = 30; + const headers = new Set<string>(["title", "author", "lastModified", "text"]); if ((this._numTotalResults === 0 || this._results.length === 0) && this._openNoResults) { - this._visibleElements = [<div className="no-result">No Search Results</div>]; + if (this.noresults === "") { + this.noresults = "No search results :("; + } return; } @@ -456,16 +645,19 @@ export class SearchBox extends React.Component<SearchProps> { else if (this._visibleElements.length !== this._numTotalResults) { // undefined until a searchitem is put in there this._visibleElements = Array<JSX.Element>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - // indicates if things are placeholders + this._visibleDocuments = Array<Doc>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + // indicates if things are placeholders this._isSearch = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); - } + this._isSorted = Array<undefined>(this._numTotalResults === -1 ? 0 : this._numTotalResults); + } for (let i = 0; i < this._numTotalResults; i++) { //if the index is out of the window then put a placeholder in //should ones that have already been found get set to placeholders? if (i < startIndex || i > endIndex) { if (this._isSearch[i] !== "placeholder") { this._isSearch[i] = "placeholder"; + this._isSorted[i] = "placeholder"; this._visibleElements[i] = <div className="searchBox-placeholder" key={`searchBox-placeholder-${i}`}>Loading...</div>; } } @@ -473,30 +665,61 @@ export class SearchBox extends React.Component<SearchProps> { if (this._isSearch[i] !== "search") { let result: [Doc, string[], string[]] | undefined = undefined; if (i >= this._results.length) { - this.getResults(this._searchString); + this.getResults(StrCast(this.layoutDoc._searchString)); if (i < this._results.length) result = this._results[i]; if (result) { const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; + const lines = new List<string>(result[2]); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + highlights.forEach((item) => headers.add(item)); + this._visibleDocuments[i] = result[0]; this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + result[0].searchMatch = true; + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; } } else { result = this._results[i]; if (result) { const highlights = Array.from([...Array.from(new Set(result[1]).values())]); - this._visibleElements[i] = <SearchItem doc={result[0]} query={this._searchString} key={result[0][Id]} lines={result[2]} highlighting={highlights} />; - this._isSearch[i] = "search"; + const lines = new List<string>(result[2]); + highlights.forEach((item) => headers.add(item)); + result[0].lines = lines; + result[0].highlighting = highlights.join(", "); + result[0].searchMatch = true; + if (i < this._visibleDocuments.length) { + this._visibleDocuments[i] = result[0]; + this._isSearch[i] = "search"; + Doc.BrushDoc(result[0]); + Doc.AddDocToList(this.dataDoc, this.props.fieldKey, result[0]); + this.children++; + } } } } } } + const schemaheaders: SchemaHeaderField[] = []; + this.headerscale = headers.size; + headers.forEach((item) => schemaheaders.push(new SchemaHeaderField(item, "#f1efeb"))); + this.headercount = schemaheaders.length; + this.props.Document._schemaHeaders = new List<SchemaHeaderField>(schemaheaders); if (this._maxSearchIndex >= this._numTotalResults) { this._visibleElements.length = this._results.length; + this._visibleDocuments.length = this._results.length; this._isSearch.length = this._results.length; } } + @observable headercount: number = 0; + @observable headerscale: number = 0; + + findCommonElements(arr2: string[]) { + const arr1 = ["layout", "data"]; + return arr1.some(item => arr2.includes(item)); + } @computed get resFull() { return this._numTotalResults <= 8; } @@ -504,165 +727,197 @@ export class SearchBox extends React.Component<SearchProps> { @computed get resultHeight() { return this._numTotalResults * 70; } - //if true, any keywords can be used. if false, all keywords are required. - @action.bound - handleWordQueryChange = () => { - this._basicWordStatus = !this._basicWordStatus; - } - - @action.bound - handleNodeChange = () => { - this._nodeStatus = !this._nodeStatus; - if (this._nodeStatus) { - this.expandSection(`node${this.props.id}`); - } - else { - this.collapseSection(`node${this.props.id}`); - } - } - - @action.bound - handleKeyChange = () => { - this._keyStatus = !this._keyStatus; - if (this._keyStatus) { - this.expandSection(`key${this.props.id}`); - } - else { - this.collapseSection(`key${this.props.id}`); - } - } - - @action.bound - handleFilterChange = () => { - this._filterOpen = !this._filterOpen; - if (this._filterOpen) { - this.expandSection(`filterhead${this.props.id}`); - document.getElementById(`filterhead${this.props.id}`)!.style.padding = "5"; - } - else { - this.collapseSection(`filterhead${this.props.id}`); + addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); + @computed get searchItemTemplate() { return Cast(Doc.UserDoc().searchItemTemplate, Doc, null); } - } + getTransform = () => { + return this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight } - - @computed - get menuHeight() { - return document.getElementById("hi")?.clientHeight; + panelHeight = () => { + return this.props.PanelHeight(); } - - - collapseSection(thing: string) { - const id = this.props.id; - const element = document.getElementById(thing)!; - // get the height of the element's inner content, regardless of its actual size - const sectionHeight = element.scrollHeight; - - // temporarily disable all css transitions - const elementTransition = element.style.transition; - element.style.transition = ''; - - // on the next frame (as soon as the previous style change has taken effect), - // explicitly set the element's height to its current pixel height, so we - // aren't transitioning out of 'auto' - requestAnimationFrame(function () { - element.style.height = sectionHeight + 'px'; - element.style.transition = elementTransition; - - // on the next frame (as soon as the previous style change has taken effect), - // have the element transition to height: 0 - requestAnimationFrame(function () { - element.style.height = 0 + 'px'; - thing === `filterhead${id}` ? document.getElementById(`filterhead${id}`)!.style.padding = "0" : null; - }); - }); - - // mark the section as "currently collapsed" - element.setAttribute('data-collapsed', 'true'); - } - - expandSection(thing: string) { - const element = document.getElementById(thing)!; - // get the height of the element's inner content, regardless of its actual size - const sectionHeight = element.scrollHeight; - - // have the element transition to the height of its inner content - element.style.height = sectionHeight + 'px'; - - // when the next css transition finishes (which should be the one we just triggered) - element.addEventListener('transitionend', function handler(e) { - // remove this event listener so it only gets triggered once - element.removeEventListener('transitionend', handler); - - // remove "height" from the element's inline styles, so it can return to its initial value - element.style.height = "auto"; - //element.style.height = undefined; - }); - - // mark the section as "currently not collapsed" - element.setAttribute('data-collapsed', 'false'); - + selectElement = (doc: Doc) => { + //this.gotoDocument(this.childDocs.indexOf(doc), NumCasst(this.layoutDoc._itemIndex)); } - autoset(thing: string) { - const element = document.getElementById(thing)!; - element.removeEventListener('transitionend', function (e) { }); - - // remove "height" from the element's inline styles, so it can return to its initial value - element.style.height = "auto"; - //element.style.height = undefined; + addDocument = (doc: Doc) => { + return null; } - @action.bound - updateTitleStatus() { this._titleFieldStatus = !this._titleFieldStatus; } - - @action.bound - updateAuthorStatus() { this._authorFieldStatus = !this._authorFieldStatus; } - - @action.bound - updateDataStatus() { this._deletedDocsStatus = !this._deletedDocsStatus; } + @observable filter = false; + //Make id layour document render() { - + this.props.Document._chromeStatus === "disabled"; + this.props.Document._searchDoc = true; + const cols = Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []).length; + let length = 0; + cols > 5 ? length = 1076 : length = cols * 205 + 51; + let height = 0; + const rows = this.children; + rows > 8 ? height = 31 + 31 * 8 : height = 31 * rows + 31; return ( - <div className="searchBox-container"> + <div style={{ pointerEvents: "all" }} className="searchBox-container"> <div className="searchBox-bar"> - <span className="searchBox-barChild searchBox-collection" onPointerDown={SetupDrag(this.collectionRef, () => this._searchString ? this.startDragCollection() : undefined)} ref={this.collectionRef} title="Drag Results as Collection"> - <FontAwesomeIcon icon="object-group" size="lg" /> - </span> - <input value={this._searchString} onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} - className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} - style={{ width: this._searchbarOpen ? "500px" : "100px" }} /> - <button className="searchBox-barChild searchBox-filter" title="Advanced Filtering Options" onClick={() => this.handleFilterChange()}><FontAwesomeIcon icon="ellipsis-v" color="white" /></button> - </div> - - <div id={`filterhead${this.props.id}`} className="filter-form" > - <div id={`filterhead2${this.props.id}`} className="filter-header" style={this._filterOpen ? {} : {}}> - <button className="filter-item" style={this._basicWordStatus ? { background: "#aaaaa3", } : {}} onClick={this.handleWordQueryChange}>Keywords</button> - <button className="filter-item" style={this._keyStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleKeyChange}>Keys</button> - <button className="filter-item" style={this._nodeStatus ? { background: "#aaaaa3" } : {}} onClick={this.handleNodeChange}>Nodes</button> - </div> - <div id={`node${this.props.id}`} className="filter-body" style={this._nodeStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> - <IconBar setIcons={(icons: string[]) => { - this._icons = icons; - }} /> - </div> - <div className="filter-key" id={`key${this.props.id}`} style={this._keyStatus ? { borderTop: "grey 1px solid" } : { borderTop: "0px" }}> - <div className="filter-keybar"> - <button className="filter-item" style={this._titleFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateTitleStatus}>Title</button> - <button className="filter-item" style={this._deletedDocsStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateDataStatus}>Deleted Docs</button> - <button className="filter-item" style={this._authorFieldStatus ? { background: "#aaaaa3", } : {}} onClick={this.updateAuthorStatus}>Author</button> + <div style={{ position: "absolute", left: 15 }}>{Doc.CurrentUserEmail}</div> + <div style={{ display: "flex", alignItems: "center" }}> + <FontAwesomeIcon onPointerDown={SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined)} icon={"search"} size="lg" + style={{ color: "black", padding: 1, left: 35, position: "relative" }} /> + + <div style={{ cursor: "default", left: 250, position: "relative", }}> + <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} ><div> + <FontAwesomeIcon icon={"filter"} size="lg" + style={{ padding: 1, backgroundColor: this.filter ? "white" : "lightgray", color: this.filter ? "black" : "white" }} + onPointerDown={e => { e.stopPropagation(); SetupDrag(this.collectionRef, () => StrCast(this.layoutDoc._searchString) ? this.startDragCollection() : undefined); }} + onClick={action(() => { + const dofilter = (currentSelectedCollection: DocumentView) => { + let docs = DocListCast(currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + const newarray: Doc[] = []; + docs.filter(d => d.data !== undefined).forEach((d) => { + d._searchDocs = new List<Doc>(this.docsforfilter); + newarray.push(...DocListCast(d.data)); + }); + docs = newarray; + } + }; + this.filter = !this.filter && !this.searchFullDB; + if (this.filter === true && this.currentSelectedCollection !== undefined) { + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>(this.docsforfilter); + + dofilter(this.currentSelectedCollection); + + this.currentSelectedCollection.props.Document._docFilters = new List<string>(Cast(this.props.Document._docFilters, listSpec("string"), [])); + this.props.Document.selectedDoc = this.currentSelectedCollection.props.Document; + } + else if (this.filter === false && this.currentSelectedCollection !== undefined) { + + dofilter(this.currentSelectedCollection); + + this.currentSelectedCollection.props.Document._searchDocs = new List<Doc>([]); + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.props.Document.selectedDoc = undefined; + } + } + )} /> + </div></Tooltip></div> + <input value={this.newsearchstring} autoComplete="off" onChange={this.onChange} type="text" placeholder="Search..." id="search-input" ref={this.inputRef} + className="searchBox-barChild searchBox-input" onPointerDown={this.openSearch} onKeyPress={this.enter} onFocus={this.openSearch} + style={{ padding: 1, paddingLeft: 20, paddingRight: 20, color: "black", height: 20, width: 250 }} /> + <div style={{ + height: 25, + paddingLeft: "4px", + paddingRight: "4px", + border: "1px solid gray", + borderRadius: "0.3em", + borderBottom: this.open === false ? "1px solid" : "none", + }}> + <form className="beta" style={{ justifyContent: "space-evenly", display: "flex" }}> + <div style={{ display: "contents" }}> + <div className="radio" style={{ margin: 0 }}> + <label style={{ fontSize: 12, marginTop: 6 }} > + <input type="radio" style={{ marginLeft: -16, marginTop: -1 }} checked={!this.searchFullDB} onChange={() => { + runInAction(() => { + this.searchFullDB = !this.searchFullDB; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._searchDocs = undefined; + this.currentSelectedCollection = undefined; + } + this.submitSearch(); + }); + }} /> + Collection + </label> + </div> + <div className="radio" style={{ margin: 0 }}> + <label style={{ fontSize: 12, marginTop: 6 }} > + <input style={{ marginLeft: -16, marginTop: -1 }} type="radio" checked={this.searchFullDB} onChange={() => { + runInAction(() => { + this.searchFullDB = !this.searchFullDB; + this.dataDoc[this.fieldKey] = new List<Doc>([]); + this.filter = false; + if (this.currentSelectedCollection !== undefined) { + let newarray: Doc[] = []; + let docs: Doc[] = []; + docs = DocListCast(this.currentSelectedCollection.dataDoc[Doc.LayoutFieldKey(this.currentSelectedCollection.dataDoc)]); + while (docs.length > 0) { + newarray = []; + docs.forEach((d) => { + if (d.data !== undefined) { + d._searchDocs = new List<Doc>(); + const newdocs = DocListCast(d.data); + newdocs.forEach((newdoc) => { + newarray.push(newdoc); + }); + } + }); + docs = newarray; + } + this.currentSelectedCollection.props.Document._docFilters = undefined; + this.currentSelectedCollection.props.Document._searchDocs = undefined; + this.currentSelectedCollection = undefined; + } + this.submitSearch(); + }); + }} /> + DB + </label> + </div> + </div> + </form> </div> </div> + + </div> + <div style={{ zIndex: 20000, color: "black" }}> + {this._searchbarOpen === true ? + <div style={{ display: "flex", justifyContent: "center", }}> + {this.noresults === "" ? <div style={{ display: this.open === true ? "flex" : "none", overflow: "auto", }}> + <CollectionView {...this.props} + Document={this.props.Document} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelHeight={this.open === true ? () => height : () => 0} + PanelWidth={this.open === true ? () => length : () => 0} + overflow={cols > 5 || rows > 8 ? true : false} + focus={this.selectElement} + ScreenToLocalTransform={Transform.Identity} + /> + </div> : + <div style={{ display: "flex", justifyContent: "center" }}><div style={{ height: 200, top: 54, minWidth: 400, position: "absolute", backgroundColor: "rgb(241, 239, 235)", display: "flex", justifyContent: "center", alignItems: "center", border: "black 1px solid", }}> + <div>{this.noresults}</div> + </div></div>} + </div> : undefined} </div> + <div className="searchBox-results" onScroll={this.resultsScrolled} style={{ display: this._resultsOpen ? "flex" : "none", height: this.resFull ? "auto" : this.resultHeight, overflow: "visibile" // this.resFull ? "auto" : "visible" }} ref={this._resultsRef}> - {this._visibleElements} </div> - </div> + </div > ); } -}
\ No newline at end of file +} diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss deleted file mode 100644 index 469f062b2..000000000 --- a/src/client/views/search/SearchItem.scss +++ /dev/null @@ -1,163 +0,0 @@ -@import "../globalCssVariables"; - -.searchItem-overview { - display: flex; - flex-direction: reverse; - justify-content: flex-end; - z-index: 0; -} - -.searchBox-placeholder, -.searchItem-overview .searchItem { - width: 100%; - background: $light-color-secondary; - border-color: $intermediate-color; - border-bottom-style: solid; - padding: 10px; - min-height: 50px; - max-height: 150px; - height: auto; - z-index: 0; - display: flex; - overflow: visible; - - .searchItem-body { - display: flex; - flex-direction: row; - width: 100%; - - .searchItem-title-container { - width: 100%; - overflow: hidden; - - .searchItem-title { - text-transform: uppercase; - text-align: left; - width: 100%; - font-weight: bold; - } - } - - .searchItem-info { - display: flex; - justify-content: flex-end; - - .icon-icons { - width: 50px - } - - .icon-live { - width: 175px; - height: 0px; - } - - .icon-icons { - height:auto; - } - .icon-icons, - .icon-live { - margin: auto; - overflow: visible; - - .searchItem-type { - display: inline-block; - width: 100%; - position: absolute; - justify-content: center; - align-items: center; - position: relative; - margin-right: 5px; - } - - .pdfBox-cont { - overflow: hidden; - - img { - width: 100% !important; - height: auto !important; - } - } - - .searchItem-type:hover+.searchItem-label { - opacity: 1; - } - - .searchItem-label { - font-size: 10; - position: relative; - right: 0px; - text-transform: capitalize; - opacity: 0; - -webkit-transition: opacity 0.2s ease-in-out; - -moz-transition: opacity 0.2s ease-in-out; - -o-transition: opacity 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; - } - } - - .icon-live:hover { - .pdfBox-cont { - img { - width: 100% !important; - } - } - } - } - - .searchItem-info:hover { - width: 60%; - } - } -} - -.searchItem:hover~.searchBox-instances, -.searchBox-instances:hover, -.searchBox-instances:active { - opacity: 1; - background: $lighter-alt-accent; -} - -.searchItem:hover { - transition: all 0.2s; - background: $lighter-alt-accent; -} - -.searchItem-highlighting { - overflow: hidden; - text-overflow: ellipsis; - white-space: pre; -} - -.searchBox-instances { - opacity: 1; - width:40px; - height:40px; - background: gray; - transition: all 0.2s ease; - color: black; - overflow: hidden; - right:-100; - display:inline-block; -} - - -.searchItem-overview:hover { - z-index: 1; -} - -.searchBox-placeholder { - min-height: 50px; - margin-left: 150px; - width: calc(100% - 150px); - text-transform: uppercase; - text-align: left; - font-weight: bold; -} - -.collection { - display: flex; -} - -.collection-item { - width: 35px; -}
\ No newline at end of file diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx deleted file mode 100644 index 2436bf418..000000000 --- a/src/client/views/search/SearchItem.tsx +++ /dev/null @@ -1,310 +0,0 @@ -import React = require("react"); -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlobeAsia, faImage, faLink, faMusic, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnFalse, Utils, returnTrue, returnOne, returnZero, returnEmptyString, returnEmptyFilter } from "../../../Utils"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentManager } from "../../util/DocumentManager"; -import { DragManager, SetupDrag } from "../../util/DragManager"; -import { SearchUtil } from "../../util/SearchUtil"; -import { Transform } from "../../util/Transform"; -import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionViewType } from "../collections/CollectionView"; -import { ParentDocSelector } from "../collections/ParentDocumentSelector"; -import { ContextMenu } from "../ContextMenu"; -import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { SearchBox } from "./SearchBox"; -import "./SearchItem.scss"; -import "./SelectorContextMenu.scss"; - -export interface SearchItemProps { - doc: Doc; - query: string; - highlighting: string[]; - lines: string[]; -} - -library.add(faCaretUp); -library.add(faObjectGroup); -library.add(faStickyNote); -library.add(faFile); -library.add(faFilePdf); -library.add(faFilm); -library.add(faMusic); -library.add(faLink); -library.add(faChartBar); -library.add(faGlobeAsia, faFingerprint); - -@observer -export class SelectorContextMenu extends React.Component<SearchItemProps> { - @observable private _docs: { col: Doc, target: Doc }[] = []; - @observable private _otherDocs: { col: Doc, target: Doc }[] = []; - - constructor(props: SearchItemProps) { - super(props); - this.fetchDocuments(); - } - - async fetchDocuments() { - const aliases = (await SearchUtil.GetViewsOfDocument(this.props.doc)).filter(doc => doc !== this.props.doc); - const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${this.props.doc[Id]}"` }); - const map: Map<Doc, Doc> = new Map; - const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs))); - allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index]))); - docs.forEach(doc => map.delete(doc)); - runInAction(() => { - this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.doc })); - this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); - - }); - } - - getOnClick({ col, target }: { col: Doc, target: Doc }) { - return () => { - col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col; - if (col._viewType === CollectionViewType.Freeform) { - const newPanX = NumCast(target.x) + NumCast(target._width) / 2; - const newPanY = NumCast(target.y) + NumCast(target._height) / 2; - col._panX = newPanX; - col._panY = newPanY; - } - CollectionDockingView.AddRightSplit(col); - }; - } - render() { - return ( - <div className="parents"> - <p className="contexts">Contexts:</p> - {[...this._docs, ...this._otherDocs].map(doc => { - const item = React.createRef<HTMLDivElement>(); - return <div className="collection" key={doc.col[Id] + doc.target[Id]} ref={item}> - <div className="collection-item" onPointerDown={ - SetupDrag(item, () => doc.col, undefined, undefined, () => SearchBox.Instance.closeSearch())}> - <FontAwesomeIcon icon={faStickyNote} /> - </div> - <a onClick={this.getOnClick(doc)}>{doc.col.title}</a> - </div>; - })} - </div> - ); - } -} - -export interface LinkMenuProps { - doc1: Doc; - doc2: Doc; -} - -@observer -export class LinkContextMenu extends React.Component<LinkMenuProps> { - - highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc); - - unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc); - - getOnClick = (col: Doc) => () => CollectionDockingView.AddRightSplit(col); - - render() { - return ( - <div className="parents"> - <p className="contexts">Anchors:</p> - <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div> - <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div> - </div> - ); - } - -} - -@observer -export class SearchItem extends React.Component<SearchItemProps> { - - @observable _selected: boolean = false; - - onClick = () => { - // I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like - DocumentManager.Instance.jumpToDocument(this.props.doc, false); - } - @observable _useIcons = true; - @observable _displayDim = 50; - - componentDidMount() { - Doc.SetSearchQuery(this.props.query); - this.props.doc.searchMatch = true; - } - componentWillUnmount() { - this.props.doc.searchMatch = undefined; - } - - //@computed - @action - public DocumentIcon() { - const layoutresult = StrCast(this.props.doc.type); - if (!this._useIcons) { - const returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); - const returnYDimension = () => this._displayDim; - const docview = <div - onPointerDown={action(() => { - this._useIcons = !this._useIcons; - this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); - })} - onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} > - <ContentFittingDocumentView - Document={this.props.doc} - LibraryPath={emptyPath} - rootSelected={returnFalse} - fitToBox={StrCast(this.props.doc.type).indexOf(DocumentType.COL) !== -1} - addDocument={returnFalse} - removeDocument={returnFalse} - addDocTab={returnFalse} - pinToPres={returnFalse} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={undefined} - ContainingCollectionView={undefined} - ScreenToLocalTransform={Transform.Identity} - renderDepth={1} - PanelWidth={returnXDimension} - PanelHeight={returnYDimension} - NativeWidth={returnZero} - NativeHeight={returnZero} - focus={emptyFunction} - moveDocument={returnFalse} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={returnFalse} - ContentScaling={returnOne} - /> - </div>; - return docview; - } - const button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf : - layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage : - layoutresult.indexOf(DocumentType.RTF) !== -1 ? faStickyNote : - layoutresult.indexOf(DocumentType.VID) !== -1 ? faFilm : - layoutresult.indexOf(DocumentType.COL) !== -1 ? faObjectGroup : - layoutresult.indexOf(DocumentType.AUDIO) !== -1 ? faMusic : - layoutresult.indexOf(DocumentType.LINK) !== -1 ? faLink : - layoutresult.indexOf(DocumentType.WEB) !== -1 ? faGlobeAsia : - faCaretUp; - return <div onClick={action(() => { this._useIcons = false; this._displayDim = Number(SEARCH_THUMBNAIL_SIZE); })} > - <FontAwesomeIcon icon={button} size="2x" /> - </div>; - } - - collectionRef = React.createRef<HTMLDivElement>(); - - @action - pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } - - nextHighlight = (e: React.PointerEvent) => { - e.preventDefault(); - e.button === 0 && SearchBox.Instance.openSearch(e); - this.props.doc.searchMatch = false; - setTimeout(() => this.props.doc.searchMatch = true, 0); - } - highlightDoc = (e: React.PointerEvent) => { - if (this.props.doc.type === DocumentType.LINK) { - if (this.props.doc.anchor1 && this.props.doc.anchor2) { - - const doc1 = Cast(this.props.doc.anchor1, Doc, null); - const doc2 = Cast(this.props.doc.anchor2, Doc, null); - Doc.BrushDoc(doc1); - Doc.BrushDoc(doc2); - } - } else { - Doc.BrushDoc(this.props.doc); - } - e.stopPropagation(); - } - - unHighlightDoc = (e: React.PointerEvent) => { - if (this.props.doc.type === DocumentType.LINK) { - if (this.props.doc.anchor1 && this.props.doc.anchor2) { - - const doc1 = Cast(this.props.doc.anchor1, Doc, null); - const doc2 = Cast(this.props.doc.anchor2, Doc, null); - Doc.UnBrushDoc(doc1); - Doc.UnBrushDoc(doc2); - } - } else { - Doc.UnBrushDoc(this.props.doc); - } - } - - onContextMenu = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ - description: "Copy ID", event: () => { - Utils.CopyText(this.props.doc[Id]); - }, - icon: "fingerprint" - }); - ContextMenu.Instance.displayMenu(e.clientX, e.clientY); - } - - _downX = 0; - _downY = 0; - _target: any; - onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => { - this._downX = e.clientX; - this._downY = e.clientY; - e.stopPropagation(); - this._target = e.currentTarget; - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMoved); - document.addEventListener("pointerup", this.onPointerUp); - } - onPointerMoved = (e: PointerEvent) => { - if (Math.abs(e.clientX - this._downX) > Utils.DRAG_THRESHOLD || - Math.abs(e.clientY - this._downY) > Utils.DRAG_THRESHOLD) { - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; - DragManager.StartDocumentDrag([this._target], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); - } - } - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMoved); - document.removeEventListener("pointerup", this.onPointerUp); - } - - @computed - get contextButton() { - return <ParentDocSelector Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />; - } - - render() { - const doc1 = Cast(this.props.doc.anchor1, Doc); - const doc2 = Cast(this.props.doc.anchor2, Doc); - return <div className="searchItem-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}> - <div className="searchItem" onPointerDown={this.nextHighlight} onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc}> - <div className="searchItem-body" onClick={this.onClick}> - <div className="searchItem-title-container"> - <div className="searchItem-title">{StrCast(this.props.doc.title)}</div> - <div className="searchItem-highlighting">{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}</div> - {this.props.lines.filter((m, i) => i).map((l, i) => <div id={i.toString()} className="searchItem-highlighting">`${l}`</div>)} - </div> - </div> - <div className="searchItem-info" style={{ width: this._useIcons ? "30px" : "100%" }}> - <div className={`icon-${this._useIcons ? "icons" : "live"}`}> - <div className="searchItem-type" title="Click to Preview" onPointerDown={this.onPointerDown}>{this.DocumentIcon()}</div> - <div className="searchItem-label">{this.props.doc.type ? this.props.doc.type : "Other"}</div> - </div> - </div> - <div className="searchItem-context" title="Drag as document"> - {(doc1 instanceof Doc && doc2 instanceof Doc) && this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> : - this.contextButton} - </div> - </div> - </div>; - } -}
\ No newline at end of file diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 9cd7eaa69..6163fedbb 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1266,4 +1266,4 @@ Scripting.addGlobal(function selectedDocs(container: Doc, excludeCollections: bo return docs.length ? new List(docs) : prevValue; }); Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: any, modifiers?: "match" | "check" | "x" | undefined) { Doc.setDocFilter(container, key, value, modifiers); }); -Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); });
\ No newline at end of file +Scripting.addGlobal(function setDocFilterRange(container: Doc, key: string, range: number[]) { Doc.setDocFilterRange(container, key, range); }); diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index 753c31fcf..7251e07a1 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -176,7 +176,7 @@ export namespace SolrManager { "audio": ["_t", "url"], "web": ["_t", "url"], "date": ["_d", value => new Date(value.date).toISOString()], - "proxy": ["_i", "fieldId"], + // "proxy": ["_i", "fieldId"], "list": ["_l", list => { const results = []; for (const value of list.fields) { diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 4455d11eb..515fbe4ff 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -14,7 +14,6 @@ import { normalize } from "path"; import RouteSubscriber from "../RouteSubscriber"; const imageDataUri = require('image-data-uri'); import { isWebUri } from "valid-url"; -import { launch } from "puppeteer"; import { Opt } from "../../fields/Doc"; import { SolrManager } from "./SearchManager"; @@ -281,25 +280,26 @@ function delay(ms: number) { * * On failure, returns undefined. */ -async function captureYoutubeScreenshot(targetUrl: string): Promise<Opt<Buffer>> { - const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); - const page = await browser.newPage(); - await page.setViewport({ width: 1920, height: 1080 }); +async function captureYoutubeScreenshot(targetUrl: string){ + // const browser = await launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + // const page = await browser.newPage(); + // // await page.setViewport({ width: 1920, height: 1080 }); - await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any }); + // // await page.goto(targetUrl, { waitUntil: 'domcontentloaded' as any }); - const videoPlayer = await page.$('.html5-video-player'); - videoPlayer && await page.focus("video"); - await delay(7000); - const ad = await page.$('.ytp-ad-skip-button-text'); - await ad?.click(); - await videoPlayer?.click(); - await delay(1000); - // hide youtube player controls. - await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none'); + // const videoPlayer = await page.$('.html5-video-player'); + // videoPlayer && await page.focus("video"); + // await delay(7000); + // const ad = await page.$('.ytp-ad-skip-button-text'); + // await ad?.click(); + // await videoPlayer?.click(); + // await delay(1000); + // // hide youtube player controls. + // await page.evaluate(() => (document.querySelector('.ytp-chrome-bottom') as HTMLElement).style.display = 'none'); - const buffer = await videoPlayer?.screenshot({ encoding: "binary" }); - await browser.close(); + // const buffer = await videoPlayer?.screenshot({ encoding: "binary" }); + // await browser.close(); - return buffer; + // return buffer; + return null; }
\ No newline at end of file diff --git a/src/server/websocket.ts b/src/server/websocket.ts index d81450b32..63cfa41f0 100644 --- a/src/server/websocket.ts +++ b/src/server/websocket.ts @@ -16,6 +16,7 @@ import { DocumentsCollection } from "./IDatabase"; import { Diff, GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, Transferable, Types, UpdateMobileInkOverlayPositionContent, YoutubeQueryInput, YoutubeQueryTypes } from "./Message"; import { Search } from "./Search"; import { resolvedPorts } from './server_Initialization'; +import { Opt } from "../fields/Doc"; export namespace WebSocket { @@ -31,7 +32,7 @@ export namespace WebSocket { if (socketPort) { resolvedPorts.socket = Number(socketPort); } - let socketEndpoint: Server; + let socketEndpoint: Opt<Server>; await new Promise<void>(resolve => socketEndpoint = createServer(SSL.Credentials, app).listen(resolvedPorts.socket, resolve)); io = sio(socketEndpoint!, SSL.Credentials as any); } else { @@ -229,7 +230,8 @@ export namespace WebSocket { "script": ["_t", value => value.script.originalScript], "RichTextField": ["_t", value => value.Text], "date": ["_d", value => new Date(value.date).toISOString()], - "proxy": ["_i", "fieldId"], + // "proxy": ["_i", "fieldId"], + // "proxy": ["", "fieldId"], "list": ["_l", list => { const results = []; for (const value of list.fields) { @@ -243,25 +245,27 @@ export namespace WebSocket { }; function ToSearchTerm(val: any): { suffix: string, value: any } | undefined { + if (val === null || val === undefined) { return; } const type = val.__type || typeof val; + let suffix = suffixMap[type]; if (!suffix) { return; } - if (Array.isArray(suffix)) { const accessor = suffix[1]; if (typeof accessor === "function") { val = accessor(val); } else { val = val[accessor]; + } suffix = suffix[0]; - } + } return { suffix, value: val }; } @@ -283,7 +287,7 @@ export namespace WebSocket { dynfield = true; const val = docfield[key]; key = key.substring(7); - Object.values(suffixMap).forEach(suf => update[key + getSuffix(suf)] = { set: null }); + Object.values(suffixMap).forEach(suf => { update[key + getSuffix(suf)] = { set: null }; }); const term = ToSearchTerm(val); if (term !== undefined) { const { suffix, value } = term; |