aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/DocServer.ts27
-rw-r--r--src/client/apis/youtube/YoutubeBox.scss222
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx2
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts6
-rw-r--r--src/client/documents/Documents.ts8
-rw-r--r--src/client/util/DictationManager.ts39
-rw-r--r--src/client/util/DragManager.ts7
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts4
-rw-r--r--src/client/util/RichTextSchema.tsx11
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/util/SerializationHelper.ts5
-rw-r--r--src/client/util/TooltipTextMenu.scss95
-rw-r--r--src/client/util/TooltipTextMenu.tsx305
-rw-r--r--src/client/views/ContextMenu.tsx8
-rw-r--r--src/client/views/DocumentDecorations.tsx5
-rw-r--r--src/client/views/GlobalKeyHandler.ts32
-rw-r--r--src/client/views/Main.scss38
-rw-r--r--src/client/views/MainOverlayTextBox.tsx1
-rw-r--r--src/client/views/MainView.tsx5
-rw-r--r--src/client/views/SearchItem.tsx69
-rw-r--r--src/client/views/TemplateMenu.tsx44
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx26
-rw-r--r--src/client/views/collections/CollectionSchemaHeaders.tsx164
-rw-r--r--src/client/views/collections/CollectionSchemaMovableTableHOC.tsx38
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss260
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx301
-rw-r--r--src/client/views/collections/CollectionStackingView.scss92
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx25
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx107
-rw-r--r--src/client/views/collections/CollectionSubView.tsx18
-rw-r--r--src/client/views/collections/CollectionTreeView.scss3
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx358
-rw-r--r--src/client/views/collections/CollectionView.tsx18
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss58
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx65
-rw-r--r--src/client/views/collections/KeyRestrictionRow.tsx6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx136
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx18
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx1
-rw-r--r--src/client/views/nodes/DocumentView.tsx73
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss2
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx100
-rw-r--r--src/client/views/nodes/ImageBox.tsx14
-rw-r--r--src/client/views/nodes/LinkEditor.tsx2
-rw-r--r--src/client/views/nodes/LinkMenuGroup.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
-rw-r--r--src/client/views/presentationview/PresentationView.scss11
-rw-r--r--src/client/views/presentationview/PresentationView.tsx19
-rw-r--r--src/client/views/search/CheckBox.scss10
-rw-r--r--src/client/views/search/FieldFilters.scss9
-rw-r--r--src/client/views/search/FilterBox.scss89
-rw-r--r--src/client/views/search/FilterBox.tsx117
-rw-r--r--src/client/views/search/IconBar.scss5
-rw-r--r--src/client/views/search/IconButton.scss14
-rw-r--r--src/client/views/search/IconButton.tsx5
-rw-r--r--src/client/views/search/SearchBox.scss5
-rw-r--r--src/client/views/search/SearchItem.tsx122
-rw-r--r--src/client/views/search/SelectorContextMenu.scss1
-rw-r--r--src/client/views/search/ToggleBar.scss9
-rw-r--r--src/client/views/search/ToggleBar.tsx1
-rw-r--r--src/debug/Viewer.tsx3
-rw-r--r--src/new_fields/Doc.ts62
-rw-r--r--src/new_fields/ObjectField.ts3
-rw-r--r--src/new_fields/RichTextField.ts2
-rw-r--r--src/new_fields/SchemaHeaderField.ts38
-rw-r--r--src/new_fields/util.ts3
-rw-r--r--src/scraping/buxton/scraper.py2
-rw-r--r--src/server/authentication/models/current_user_utils.ts2
-rw-r--r--src/server/database.ts56
-rw-r--r--src/server/index.ts187
-rw-r--r--src/server/remapUrl.ts7
72 files changed, 2534 insertions, 1080 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 258acd9cd..fc39fa364 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -124,16 +124,17 @@ export namespace DocServer {
// future .proto calls on the Doc won't have to go farther than the cache to get their actual value.
const deserializeField = getSerializedField.then(async fieldJson => {
// deserialize
- const field = await SerializationHelper.Deserialize(fieldJson);
+ const field = await SerializationHelper.Deserialize(fieldJson, val => {
+ if (val !== undefined) {
+ _cache[id] = val;
+ } else {
+ delete _cache[id];
+ }
+ });
+ return field;
// either way, overwrite or delete any promises cached at this id (that we inserted as flags
// to indicate that the field was in the process of being fetched). Now everything
// should be an actual value within or entirely absent from the cache.
- if (field !== undefined) {
- _cache[id] = field;
- } else {
- delete _cache[id];
- }
- return field;
});
// here, indicate that the document associated with this id is currently
// being retrieved and cached
@@ -214,15 +215,23 @@ export namespace DocServer {
const deserializeFields = getSerializedFields.then(async fields => {
const fieldMap: { [id: string]: RefField } = {};
// const protosToLoad: any = [];
+ const proms: Promise<RefField>[] = [];
for (const field of fields) {
if (field !== undefined) {
// deserialize
- let deserialized = await SerializationHelper.Deserialize(field);
- fieldMap[field.id] = deserialized;
+ let prom = SerializationHelper.Deserialize(field, val => {
+ if (val !== undefined) {
+ _cache[field.id] = field;
+ } else {
+ delete _cache[field.id];
+ }
+ }).then(deserialized => fieldMap[field.id] = deserialized);
+ proms.push(prom);
// adds to a list of promises that will be awaited asynchronously
// protosToLoad.push(deserialized.proto);
}
}
+ await Promise.all(proms);
// this actually handles the loading of prototypes
// await Promise.all(protosToLoad);
return fieldMap;
diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss
index 1fc91a9ae..eabdbb1ac 100644
--- a/src/client/apis/youtube/YoutubeBox.scss
+++ b/src/client/apis/youtube/YoutubeBox.scss
@@ -1,124 +1,126 @@
-ul {
- list-style-type: none;
- padding-inline-start: 10px;
-}
-
-
-li {
- margin: 4px;
- display: inline-flex;
-}
-
-li:hover {
- cursor: pointer;
- opacity: 0.8;
-}
-
-.search_wrapper {
- width: 100%;
- display: inline-flex;
- height: 175px;
-
- .video_duration {
- // margin: 0;
- // padding: 0;
- border: 0;
- background: transparent;
- display: inline-block;
- position: relative;
- bottom: 25px;
- left: 85%;
+.youtubeBox-cont {
+ ul {
+ list-style-type: none;
+ padding-inline-start: 10px;
+ }
+
+
+ li {
margin: 4px;
- color: #FFFFFF;
- background-color: rgba(0, 0, 0, 0.80);
- padding: 2px 4px;
- border-radius: 2px;
- letter-spacing: .5px;
- font-size: 1.2rem;
- font-weight: 500;
- line-height: 1.2rem;
+ display: inline-flex;
+ }
+ li:hover {
+ cursor: pointer;
+ opacity: 0.8;
}
- .textual_info {
- font-family: Arial, Helvetica, sans-serif;
-
- .videoTitle {
- margin-left: 4px;
- // display: inline-block;
- color: #0D0D0D;
- -webkit-line-clamp: 2;
- display: block;
- max-height: 4.8rem;
- overflow: hidden;
- font-size: 1.8rem;
- font-weight: 400;
- line-height: 2.4rem;
- -webkit-box-orient: vertical;
- text-overflow: ellipsis;
- white-space: normal;
- display: -webkit-box;
- }
+ .search_wrapper {
+ width: 100%;
+ display: inline-flex;
+ height: 175px;
- .channelName {
- color:#606060;
- margin-left: 4px;
- font-size: 1.3rem;
- font-weight: 400;
- line-height: 1.8rem;
- text-transform: none;
- margin-top: 0px;
+ .video_duration {
+ // margin: 0;
+ // padding: 0;
+ border: 0;
+ background: transparent;
display: inline-block;
- }
+ position: relative;
+ bottom: 25px;
+ left: 85%;
+ margin: 4px;
+ color: #FFFFFF;
+ background-color: rgba(0, 0, 0, 0.80);
+ padding: 2px 4px;
+ border-radius: 2px;
+ letter-spacing: .5px;
+ font-size: 1.2rem;
+ font-weight: 500;
+ line-height: 1.2rem;
- .video_description {
- margin-left: 4px;
- // font-size: 12px;
- color: #606060;
- padding-top: 8px;
- margin-bottom: 8px;
- display: block;
- line-height: 1.8rem;
- max-height: 4.2rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
}
- .publish_time {
- //display: inline-block;
- margin-left: 8px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #606060;
- max-width: 100%;
- line-height: 1.8rem;
- max-height: 3.6rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
- }
+ .textual_info {
+ font-family: Arial, Helvetica, sans-serif;
+
+ .videoTitle {
+ margin-left: 4px;
+ // display: inline-block;
+ color: #0D0D0D;
+ -webkit-line-clamp: 2;
+ display: block;
+ max-height: 4.8rem;
+ overflow: hidden;
+ font-size: 1.8rem;
+ font-weight: 400;
+ line-height: 2.4rem;
+ -webkit-box-orient: vertical;
+ text-overflow: ellipsis;
+ white-space: normal;
+ display: -webkit-box;
+ }
+
+ .channelName {
+ color: #606060;
+ margin-left: 4px;
+ font-size: 1.3rem;
+ font-weight: 400;
+ line-height: 1.8rem;
+ text-transform: none;
+ margin-top: 0px;
+ display: inline-block;
+ }
+
+ .video_description {
+ margin-left: 4px;
+ // font-size: 12px;
+ color: #606060;
+ padding-top: 8px;
+ margin-bottom: 8px;
+ display: block;
+ line-height: 1.8rem;
+ max-height: 4.2rem;
+ overflow: hidden;
+ font-size: 1.3rem;
+ font-weight: 400;
+ text-transform: none;
+ }
+
+ .publish_time {
+ //display: inline-block;
+ margin-left: 8px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: #606060;
+ max-width: 100%;
+ line-height: 1.8rem;
+ max-height: 3.6rem;
+ overflow: hidden;
+ font-size: 1.3rem;
+ font-weight: 400;
+ text-transform: none;
+ }
+
+ .viewCount {
+
+ margin-left: 8px;
+ padding: 0;
+ border: 0;
+ background: transparent;
+ color: #606060;
+ max-width: 100%;
+ line-height: 1.8rem;
+ max-height: 3.6rem;
+ overflow: hidden;
+ font-size: 1.3rem;
+ font-weight: 400;
+ text-transform: none;
+ }
+
- .viewCount {
- margin-left: 8px;
- padding: 0;
- border: 0;
- background: transparent;
- color: #606060;
- max-width: 100%;
- line-height: 1.8rem;
- max-height: 3.6rem;
- overflow: hidden;
- font-size: 1.3rem;
- font-weight: 400;
- text-transform: none;
}
-
-
-
}
} \ No newline at end of file
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index 7f9a3ad70..dc142802c 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -343,7 +343,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
render() {
let content =
- <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
+ <div className="youtubeBox-cont" style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
<input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: "100%", border: "1px solid black", padding: 5, textAlign: "center" }} ref={(e) => this.YoutubeSearchElement = e!} />
{this.renderSearchResultsOrVideo()}
</div>;
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 076907f09..c118d91d3 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,15 +1,11 @@
import * as request from "request-promise";
import { Doc, Field, Opt } from "../../new_fields/Doc";
import { Cast } from "../../new_fields/Types";
-import { ImageField } from "../../new_fields/URLField";
-import { List } from "../../new_fields/List";
import { Docs } from "../documents/Documents";
import { RouteStore } from "../../server/RouteStore";
import { Utils } from "../../Utils";
-import { CompileScript } from "../util/Scripting";
-import { ComputedField } from "../../new_fields/ScriptField";
import { InkData } from "../../new_fields/InkField";
-import { undoBatch, UndoManager } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor, analyzer: AnalysisApplier };
type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2dcf655e3..2a1f63d59 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -414,7 +414,7 @@ export namespace Docs {
}
export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Freeform });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform });
}
export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
@@ -422,15 +422,15 @@ export namespace Docs {
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Tree });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Tree });
}
export function StackingDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Stacking });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Stacking });
}
export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
- return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Masonry });
+ return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Masonry });
}
export function ButtonDocument(options?: DocumentOptions) {
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
new file mode 100644
index 000000000..b58bdb6c7
--- /dev/null
+++ b/src/client/util/DictationManager.ts
@@ -0,0 +1,39 @@
+namespace CORE {
+ export interface IWindow extends Window {
+ webkitSpeechRecognition: any;
+ }
+}
+
+const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
+
+export default class DictationManager {
+ public static Instance = new DictationManager();
+ private isListening = false;
+ private recognizer: any;
+
+ constructor() {
+ this.recognizer = new webkitSpeechRecognition();
+ this.recognizer.interimResults = false;
+ this.recognizer.continuous = true;
+ }
+
+ finish = (handler: any, data: any) => {
+ handler(data);
+ this.isListening = false;
+ this.recognizer.stop();
+ }
+
+ listen = () => {
+ if (this.isListening) {
+ return undefined;
+ }
+ this.isListening = true;
+ this.recognizer.start();
+ return new Promise<string>((resolve, reject) => {
+ this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript);
+ this.recognizer.onerror = (e: any) => this.finish(reject, e);
+ });
+
+ }
+
+} \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index abcc3a4e1..0b5c785a4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -10,6 +10,7 @@ import { LinkManager } from "./LinkManager";
import { SelectionManager } from "./SelectionManager";
import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
import { DocumentDecorations } from "../views/DocumentDecorations";
+import { NumberLiteralType } from "typescript";
export type dropActionType = "alias" | "copy" | undefined;
export function SetupDrag(
@@ -140,6 +141,10 @@ export namespace DragManager {
dragHasStarted?: () => void;
withoutShiftDrag?: boolean;
+
+ offsetX?: number;
+
+ offsetY?: number;
}
export interface DragDropDisposer {
@@ -423,7 +428,7 @@ export namespace DragManager {
lastX = e.pageX;
lastY = e.pageY;
dragElements.map((dragElement, i) => (dragElement.style.transform =
- `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
+ `translate(${(xs[i] += moveX) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`)
);
};
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index fa9e2e5af..c38f84551 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -47,6 +47,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
bind("Mod-i", toggleMark(type));
bind("Mod-I", toggleMark(type));
}
+ if (type = schema.marks.underline) {
+ bind("Mod-u", toggleMark(type));
+ bind("Mod-U", toggleMark(type));
+ }
if (type = schema.marks.code) {
bind("Mod-`", toggleMark(type));
}
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 269de0f42..ce9e29b26 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -283,7 +283,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
highlight: {
- parseDOM: [{ style: 'background: #d9dbdd' }],
+ parseDOM: [{ style: 'color: blue' }],
toDOM() {
return ['span', {
style: 'color: blue'
@@ -291,6 +291,15 @@ export const marks: { [index: string]: MarkSpec } = {
}
},
+ search_highlight: {
+ parseDOM: [{ style: 'background: yellow' }],
+ toDOM() {
+ return ['span', {
+ style: 'background: yellow'
+ }];
+ }
+ },
+
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 9efef888d..ee623d082 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -4,6 +4,7 @@ import { DocumentView } from "../views/nodes/DocumentView";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { NumCast, StrCast } from "../../new_fields/Types";
import { InkingControl } from "../views/InkingControl";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
export namespace SelectionManager {
diff --git a/src/client/util/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 034be8f67..13302be21 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,6 +1,7 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema, primitive, SKIP } from "serializr";
import { Field, Doc } from "../../new_fields/Doc";
import { ClientUtils } from "./ClientUtils";
+import { emptyFunction } from "../../Utils";
let serializing = 0;
export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
@@ -33,7 +34,7 @@ export namespace SerializationHelper {
return json;
}
- export async function Deserialize(obj: any): Promise<any> {
+ export async function Deserialize(obj: any, cb: (val: any) => void = emptyFunction): Promise<any> {
if (obj === undefined || obj === null) {
return undefined;
}
@@ -56,7 +57,7 @@ export namespace SerializationHelper {
}
const type = serializationTypes[obj.__type];
- const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
+ const value = await new Promise(res => cb(deserialize(type.ctor, obj, (err, result) => res(result))));
if (type.afterDeserialize) {
await type.afterDeserialize(value);
}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 40ac3abb9..ebf833dbe 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -18,7 +18,8 @@
.ProseMirror-menuitem {
margin-right: 3px;
display: inline-block;
- z-index: 100000;
+ z-index: 50000;
+ position: relative;
}
.ProseMirror-menuseparator {
@@ -67,7 +68,7 @@
}
.ProseMirror-menu-dropdown-menu {
- z-index: 100000;
+ z-index: 50000;
min-width: 6em;
background: white;
position: absolute;
@@ -235,8 +236,8 @@
}
.tooltipMenu {
- position: relative;
- z-index: 2000;
+ position: absolute;
+ z-index: 20000;
background: #121721;
border: 1px solid silver;
border-radius: 15px;
@@ -247,7 +248,7 @@
//transform: translateX(-50%);
transform: translateY(-85px);
pointer-events: all;
- height: 30px;
+ height: fit-content;
width:550px;
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
@@ -264,28 +265,40 @@
}
}
-// .tooltipMenu:before {
-// content: "";
-// height: 0; width: 0;
-// position: absolute;
-// left: 50%;
-// margin-left: -5px;
-// bottom: -6px;
-// border: 5px solid transparent;
-// border-bottom-width: 0;
-// border-top-color: silver;
-// }
-// .tooltipMenu:after {
-// content: "";
-// height: 0; width: 0;
-// position: absolute;
-// left: 50%;
-// margin-left: -5px;
-// bottom: -4.5px;
-// border: 5px solid transparent;
-// border-bottom-width: 0;
-// border-top-color: $dark-color;
-// }
+.tooltipExtras {
+ position: absolute;
+ z-index: 20000;
+ background: #121721;
+ border: 1px solid silver;
+ border-radius: 15px;
+ //height: 60px;
+ //padding: 2px 10px;
+ //margin-top: 100px;
+ //-webkit-transform: translateX(-50%);
+ //transform: translateX(-50%);
+ transform: translateY(-115px);
+ pointer-events: all;
+ height: 25px;
+ width:fit-content;
+ .ProseMirror-example-setup-style hr {
+ padding: 2px 10px;
+ border: none;
+ margin: 1em 0;
+ }
+
+ .ProseMirror-example-setup-style hr:after {
+ content: "";
+ display: block;
+ height: 1px;
+ background-color: silver;
+ line-height: 2px;
+ }
+}
+
+.wrapper {
+ position: absolute;
+ pointer-events: all;
+}
.menuicon {
display: inline-block;
@@ -298,6 +311,7 @@
cursor: pointer;
text-align: center;
min-width: 10px;
+
}
.strong, .heading { font-weight: bold; }
.em { font-style: italic; }
@@ -310,9 +324,32 @@
padding-right: 0px;
}
.summarize{
- //margin-left: 15px;
color: white;
height: 20px;
- // background-color: white;
text-align: center;
+ }
+
+ .brush{
+ display: inline-block;
+ width: 1em;
+ height: 1em;
+ stroke-width: 0;
+ stroke: currentColor;
+ fill: currentColor;
+ margin-right: 15px;
+ }
+
+ .brush-active{
+ display: inline-block;
+ width: 1em;
+ height: 1em;
+ stroke-width: 3;
+ stroke: greenyellow;
+ fill: greenyellow;
+ margin-right: 15px;
+ }
+
+ .dragger{
+ color: #eee;
+ margin-left: 5px;
} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 6214b568c..d33a52d7f 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,4 +1,6 @@
-import { action } from "mobx";
+import { action, observable, observe } from "mobx";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTag, faPlus, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
import { Dropdown, MenuItem, icons, } from "prosemirror-menu"; //no import css
import { EditorState, NodeSelection, TextSelection, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
@@ -18,8 +20,13 @@ import { DocServer } from "../DocServer";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { DocumentManager } from "./DocumentManager";
import { Id } from "../../new_fields/FieldSymbols";
-import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
+import { FormattedTextBoxProps, FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { typeAlias } from "babel-types";
+import React, { Children } from "react";
+import ReactDOM from "react-dom";
import { Utils } from "../../Utils";
+import { LinkManager } from "./LinkManager";
+import { bool } from "prop-types";
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
@@ -33,6 +40,9 @@ export class TooltipTextMenu {
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
private listTypeToIcon: Map<NodeType, string>;
+ //private link: HTMLAnchorElement;
+ private wrapper: HTMLDivElement;
+ private extras: HTMLDivElement;
private linkEditor?: HTMLDivElement;
private linkText?: HTMLDivElement;
@@ -46,13 +56,42 @@ export class TooltipTextMenu {
private _collapseBtn?: MenuItem;
+ private _brushMarks?: Set<Mark>;
+ private _brushIsEmpty: boolean = true;
+ private _brushdom?: Node;
+
+ private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
+
+ private _collapsed: boolean = false;
+
+ @observable
+ private _storedMarks: Mark<any>[] | null | undefined;
+
+
constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
this.view = view;
this.editorProps = editorProps;
+
+ this.wrapper = document.createElement("div");
this.tooltip = document.createElement("div");
+ this.extras = document.createElement("div");
+
+ this.wrapper.appendChild(this.extras);
+ this.wrapper.appendChild(this.tooltip);
+
this.tooltip.className = "tooltipMenu";
+ this.extras.className = "tooltipExtras";
+ this.wrapper.className = "wrapper";
+
+ const dragger = document.createElement("span");
+ dragger.className = "dragger";
+ dragger.textContent = ">>>";
+ this.extras.appendChild(dragger);
+
+ this.dragElement(dragger);
+
+ this._storedMarks = this.view.state.storedMarks;
- this.dragElement(this.tooltip);
// this.createCollapse();
// if (this._collapseBtn) {
// this.tooltip.appendChild(this._collapseBtn.render(this.view).dom);
@@ -71,13 +110,23 @@ export class TooltipTextMenu {
{ command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
{ command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
{ command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') }
- // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
- // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
- // { command: lift, dom: this.icon("<", "lift") },
];
+
+ this._marksToDoms = new Map();
//add menu items
items.forEach(({ dom, command }) => {
this.tooltip.appendChild(dom);
+ switch (dom.title) {
+ case "Bold":
+ this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
+ break;
+ case "Italic":
+ this._marksToDoms.set(schema.mark(schema.marks.em), dom);
+ break;
+ case "Underline":
+ this._marksToDoms.set(schema.mark(schema.marks.underline), dom);
+ break;
+ }
//pointer down handler to activate button effects
dom.addEventListener("pointerdown", e => {
@@ -86,12 +135,17 @@ export class TooltipTextMenu {
if (dom.contains(e.target as Node)) {
e.stopPropagation();
command(view.state, view.dispatch, view);
+ // if (this.view.state.selection.empty) {
+ // if (dom.style.color === "white") { dom.style.color = "greenyellow"; }
+ // else { dom.style.color = "white"; }
+ // }
}
});
});
this.updateLinkMenu();
+
//list of font styles
this.fontStylesToName = new Map();
this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman");
@@ -125,21 +179,23 @@ export class TooltipTextMenu {
this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
this.listTypes = Array.from(this.listTypeToIcon.keys());
+ //custom tools
// this.tooltip.appendChild(this.createLink().render(this.view).dom);
+ this._brushdom = this.createBrush().render(this.view).dom;
+ this.tooltip.appendChild(this._brushdom);
+ this.tooltip.appendChild(this.createLink().render(this.view).dom);
this.tooltip.appendChild(this.createStar().render(this.view).dom);
-
-
this.updateListItemDropdown(":", this.listTypeBtnDom);
this.update(view, undefined);
//view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
- // quick and dirty null check
+ // add tooltip to outerdiv to circumvent scaling problem
const outer_div = this.editorProps.outer_div;
- outer_div && outer_div(this.tooltip);
+ outer_div && outer_div(this.wrapper);
}
//label of dropdown will change to given label
@@ -164,6 +220,8 @@ export class TooltipTextMenu {
this.fontSizeDom = newfontSizeDom;
}
+ // Make the DIV element draggable
+
//label of dropdown will change to given label
updateFontStyleDropdown(label: string) {
//filtering function - might be unecessary
@@ -259,6 +317,8 @@ export class TooltipTextMenu {
},
hideSource: false
});
+ e.stopPropagation();
+ e.preventDefault();
};
this.linkEditor.appendChild(this.linkDrag);
// this.linkEditor.appendChild(this.linkText);
@@ -285,6 +345,7 @@ export class TooltipTextMenu {
if (elmnt) {
// if present, the header is where you move the DIV from:
elmnt.onpointerdown = dragMouseDown;
+ elmnt.ondblclick = onClick;
}
const self = this;
@@ -299,6 +360,17 @@ export class TooltipTextMenu {
document.onpointermove = elementDrag;
}
+ function onClick(e: MouseEvent) {
+ self._collapsed = !self._collapsed;
+ const children = self.wrapper.childNodes;
+ if (self._collapsed && children.length > 1) {
+ self.wrapper.removeChild(self.tooltip);
+ }
+ else {
+ self.wrapper.appendChild(self.tooltip);
+ }
+ }
+
function elementDrag(e: PointerEvent) {
e = e || window.event;
//e.preventDefault();
@@ -308,8 +380,11 @@ export class TooltipTextMenu {
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
- elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
- elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
+ // elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
+ // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
+
+ self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px";
+ self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px";
}
function closeDragElement() {
@@ -334,6 +409,27 @@ export class TooltipTextMenu {
link = node && node.marks.find(m => m.type.name === "link");
}
+ deleteLink = () => {
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = node && node.marks.find(m => m.type.name === "link");
+ let href = link!.attrs.href;
+ if (href) {
+ if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+ const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
+ if (linkclicked) {
+ DocServer.GetRefField(linkclicked).then(async linkDoc => {
+ if (linkDoc instanceof Doc) {
+ LinkManager.Instance.deleteLink(linkDoc);
+ this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
+ }
+ });
+ }
+ }
+ }
+
+
+ }
+
public static insertStar(state: EditorState<any>, dispatch: any) {
let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from });
if (dispatch) {
@@ -367,7 +463,7 @@ export class TooltipTextMenu {
}
//for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
- changeToMarkInGroup = (markType: MarkType, view: EditorView, fontMarks: MarkType[]) => {
+ changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
let { $cursor, ranges } = view.state.selection as TextSelection;
let state = view.state;
let dispatch = view.dispatch;
@@ -393,17 +489,23 @@ export class TooltipTextMenu {
}
}
});
- // fontsize
- if (markType.name[0] === 'p') {
- let size = this.fontSizeToNum.get(markType);
- if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+
+ if (markType) {
+ // fontsize
+ if (markType.name[0] === 'p') {
+ let size = this.fontSizeToNum.get(markType);
+ if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+ }
+ else {
+ let fontName = this.fontStylesToName.get(markType);
+ if (fontName) { this.updateFontStyleDropdown(fontName); }
+ }
+ //actually apply font
+ return toggleMark(markType)(view.state, view.dispatch, view);
}
else {
- let fontName = this.fontStylesToName.get(markType);
- if (fontName) { this.updateFontStyleDropdown(fontName); }
+ return;
}
- //actually apply font
- return toggleMark(markType)(view.state, view.dispatch, view);
}
//remove all node typeand apply the passed-in one to the selected text
@@ -446,6 +548,85 @@ export class TooltipTextMenu {
});
}
+ deleteLinkItem() {
+ const icon = {
+ height: 16, width: 16,
+ path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z"
+ };
+ return new MenuItem({
+ title: "Delete Link",
+ label: "X",
+ icon: icon,
+ css: "color: red",
+ class: "summarize",
+ execEvent: "",
+ run: (state, dispatch) => {
+ this.deleteLink();
+ }
+ });
+ }
+
+ createBrush(active: boolean = false) {
+ const icon = {
+ height: 32, width: 32,
+ path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z"
+ };
+ return new MenuItem({
+ title: "Brush tool",
+ label: "Brush tool",
+ icon: icon,
+ css: "color:white;",
+ class: active ? "brush-active" : "brush",
+ execEvent: "",
+ run: (state, dispatch) => {
+ this.brush_function(state, dispatch);
+ },
+ active: (state) => {
+ return true;
+ }
+ });
+ }
+
+ // selectionchanged event handler
+
+ brush_function(state: EditorState<any>, dispatch: any) {
+ if (this._brushIsEmpty) {
+ const selected_marks = this.getMarksInSelection(this.view.state);
+ if (this._brushdom) {
+ if (selected_marks.size >= 0) {
+ this._brushMarks = selected_marks;
+ const newbrush = this.createBrush(true).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
+ this._brushIsEmpty = !this._brushIsEmpty;
+ }
+ }
+ }
+ else {
+ let { from, to, $from } = this.view.state.selection;
+ if (this._brushdom) {
+ if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
+ if (this._brushMarks && to - from > 0) {
+ this.view.dispatch(this.view.state.tr.removeMark(from, to));
+ this._brushMarks.forEach((mark: Mark) => {
+ const markType = mark.type;
+ this.changeToMarkInGroup(markType, this.view, []);
+
+ });
+ }
+ }
+ else {
+ const newbrush = this.createBrush(false).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
+ this._brushIsEmpty = !this._brushIsEmpty;
+ }
+ }
+ }
+
+
+ }
+
createCollapse() {
this._collapseBtn = new MenuItem({
title: "Collapse",
@@ -601,20 +782,29 @@ export class TooltipTextMenu {
};
}
- getMarksInSelection(state: EditorState<any>, targets: MarkType<any>[]) {
- let found: Mark<any>[] = [];
+ getMarksInSelection(state: EditorState<any>) {
+ let found = new Set<Mark>();
let { from, to } = state.selection as TextSelection;
state.doc.nodesBetween(from, to, (node) => {
let marks = node.marks;
if (marks) {
marks.forEach(m => {
- if (targets.includes(m.type)) found.push(m);
+ found.add(m);
});
}
});
return found;
}
+ reset_mark_doms() {
+ let iterator = this._marksToDoms.values();
+ let next = iterator.next();
+ while (!next.done) {
+ next.value.style.color = "white";
+ next = iterator.next();
+ }
+ }
+
//updates the tooltip menu when the selection changes
update(view: EditorView, lastState: EditorState | undefined) {
let state = view.state;
@@ -622,13 +812,13 @@ export class TooltipTextMenu {
if (lastState && lastState.doc.eq(state.doc) &&
lastState.selection.eq(state.selection)) return;
+ this.reset_mark_doms();
+
// Hide the tooltip if the selection is empty
if (state.selection.empty) {
//this.tooltip.style.display = "none";
//return;
}
-
-
//UPDATE LIST ITEM DROPDOWN
//UPDATE FONT STYLE DROPDOWN
@@ -666,12 +856,55 @@ export class TooltipTextMenu {
}
}
this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks));
+
+ this.update_mark_doms();
+ }
+
+ public mark_key_pressed(marks: Mark<any>[]) {
+ if (this.view.state.selection.empty) {
+ if (marks) this._activeMarks = marks;
+ this.update_mark_doms();
+ }
+ }
+
+ update_mark_doms() {
+ this.reset_mark_doms();
+ let foundlink = false;
+ let children = this.extras.childNodes;
+ this._activeMarks.forEach((mark) => {
+ if (this._marksToDoms.has(mark)) {
+ let dom = this._marksToDoms.get(mark);
+ if (dom) dom.style.color = "greenyellow";
+ }
+ if (children.length > 1) {
+ foundlink = true;
+ }
+ if (mark.type.name === "link" && children.length === 1) {
+ // let del = document.createElement("button");
+ // del.textContent = "X";
+ // del.style.color = "red";
+ // del.style.height = "10px";
+ // del.style.width = "10px";
+ // del.style.marginLeft = "5px";
+ // del.onclick = this.deleteLink;
+ // this.extras.appendChild(del);
+ let del = this.deleteLinkItem().render(this.view).dom;
+ this.extras.appendChild(del);
+ foundlink = true;
+ }
+ });
+ if (!foundlink) {
+ if (children.length > 1) {
+ this.extras.removeChild(children[1]);
+ }
+ }
+
}
//finds all active marks on selection in given group
activeMarksOnSelection(markGroup: MarkType[]) {
//current selection
- let { empty, ranges } = this.view.state.selection as TextSelection;
+ let { empty, ranges, $to } = this.view.state.selection as TextSelection;
let state = this.view.state;
let dispatch = this.view.dispatch;
let activeMarks: MarkType[];
@@ -686,6 +919,9 @@ export class TooltipTextMenu {
}
return false;
});
+
+ const refnode = this.reference_node($to);
+ this._activeMarks = refnode.marks;
}
else {
const pos = this.view.state.selection.$from;
@@ -696,9 +932,7 @@ export class TooltipTextMenu {
else {
return [];
}
-
this._activeMarks = ref_node.marks;
-
activeMarks = markGroup.filter(mark_type => {
if (dispatch) {
let mark = state.schema.mark(mark_type);
@@ -717,12 +951,12 @@ export class TooltipTextMenu {
reference_node(pos: ResolvedPos<any>): ProsNode {
let ref_node: ProsNode = this.view.state.doc;
- if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
- ref_node = pos.nodeAfter;
- }
- else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+ if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
ref_node = pos.nodeBefore;
}
+ else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+ ref_node = pos.nodeAfter;
+ }
else if (pos.pos > 0) {
let skip = false;
for (let i: number = pos.pos - 1; i > 0; i--) {
@@ -735,10 +969,13 @@ export class TooltipTextMenu {
});
}
}
+ if (!ref_node.isLeaf) {
+ ref_node = ref_node.child(0);
+ }
return ref_node;
}
destroy() {
- this.tooltip.remove();
+ this.wrapper.remove();
}
}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index a608e448a..1bf6e383d 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -38,8 +38,12 @@ export class ContextMenu extends React.Component {
this._items = [];
}
- findByDescription = (target: string) => {
- return this._items.find(menuItem => menuItem.description === target);
+ findByDescription = (target: string, toLowerCase = false) => {
+ return this._items.find(menuItem => {
+ let reference = menuItem.description;
+ toLowerCase && (reference = reference.toLowerCase());
+ return reference === target;
+ });
}
@action
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 40f2c3da9..15471371a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -29,6 +29,7 @@ import { LinkManager } from '../util/LinkManager';
import { ObjectField } from '../../new_fields/ObjectField';
import { MetadataEntryMenu } from './MetadataEntryMenu';
import { ImageBox } from './nodes/ImageBox';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -144,7 +145,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
- if (documentView.props.renderDepth === 0) {
+ if (documentView.props.renderDepth === 0 ||
+ Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) {
return bounds;
}
let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse();
@@ -539,7 +541,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (fixedAspect && (!nwidth || !nheight)) {
proto.nativeWidth = nwidth = doc.width || 0;
proto.nativeHeight = nheight = doc.height || 0;
- proto.ignoreAspect = true;
}
if (nwidth > 0 && nheight > 0 && !BoolCast(proto.ignoreAspect)) {
if (Math.abs(dW) > Math.abs(dH)) {
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e31b44514..ea2e3e196 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -5,9 +5,13 @@ import { MainView } from "./MainView";
import { DragManager } from "../util/DragManager";
import { action } from "mobx";
import { Doc } from "../../new_fields/Doc";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import DictationManager from "../util/DictationManager";
+import { ContextMenu } from "./ContextMenu";
+import { ContextMenuProps } from "./ContextMenuItem";
const modifiers = ["control", "meta", "shift", "alt"];
-type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo;
+type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
type KeyControlInfo = {
preventDefault: boolean,
stopPropagation: boolean
@@ -25,9 +29,10 @@ export default class KeyManager {
this.router.set(isMac ? "0001" : "0100", this.ctrl);
this.router.set(isMac ? "0100" : "0010", this.alt);
this.router.set(isMac ? "1001" : "1100", this.ctrl_shift);
+ this.router.set("1000", this.shift);
}
- public handle = (e: KeyboardEvent) => {
+ public handle = async (e: KeyboardEvent) => {
let keyname = e.key.toLowerCase();
this.handleGreedy(keyname);
@@ -43,7 +48,7 @@ export default class KeyManager {
return;
}
- let control = handleConstrained(keyname, e);
+ let control = await handleConstrained(keyname, e);
control.stopPropagation && e.stopPropagation();
control.preventDefault && e.preventDefault();
@@ -95,6 +100,27 @@ export default class KeyManager {
};
});
+ private shift = async (keyname: string) => {
+ let stopPropagation = false;
+ let preventDefault = false;
+
+ switch (keyname) {
+ case " ":
+ let transcript = await DictationManager.Instance.listen();
+ console.log(`I heard${transcript ? `: ${transcript.toLowerCase()}` : " nothing: I thought I was still listening from an earlier session."}`);
+ let command: ContextMenuProps | undefined;
+ transcript && (command = ContextMenu.Instance.findByDescription(transcript, true)) && "event" in command && command.event();
+ stopPropagation = true;
+ preventDefault = true;
+ break;
+ }
+
+ return {
+ stopPropagation: stopPropagation,
+ preventDefault: preventDefault
+ };
+ }
+
private alt = action((keyname: string) => {
let stopPropagation = true;
let preventDefault = true;
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index a16123476..eed2ae4fa 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -24,7 +24,7 @@ div {
.jsx-parser {
width: 100%;
- height:100%;
+ height: 100%;
pointer-events: none;
border-radius: inherit;
}
@@ -119,6 +119,7 @@ button:hover {
margin-bottom: 10px;
}
}
+
.toolbar-color-picker {
background-color: $light-color;
border-radius: 5px;
@@ -128,6 +129,7 @@ button:hover {
left: -3px;
box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
}
+
.toolbar-color-button {
border-radius: 11px;
width: 22px;
@@ -146,7 +148,7 @@ button:hover {
bottom: 22px;
left: 250px;
- > label {
+ >label {
background: $dark-color;
color: $light-color;
display: inline-block;
@@ -168,15 +170,15 @@ button:hover {
transform: scale(1.15);
}
- > input {
+ >input {
display: none;
}
- > input:not(:checked)~#add-options-content {
+ >input:not(:checked)~#add-options-content {
display: none;
}
- > input:checked~label {
+ >input:checked~label {
transform: rotate(45deg);
transition: transform 0.5s;
cursor: pointer;
@@ -221,7 +223,7 @@ ul#add-options-list {
list-style: none;
padding: 5 0 0 0;
- > li {
+ >li {
display: inline-block;
padding: 0;
}
@@ -231,7 +233,7 @@ ul#add-options-list {
height: 100%;
position: absolute;
display: flex;
- flex-direction:column;
+ flex-direction: column;
}
.mainView-libraryHandle {
@@ -243,21 +245,25 @@ ul#add-options-list {
position: absolute;
z-index: 1;
}
+
.svg-inline--fa {
vertical-align: unset;
}
+
.mainView-workspace {
- height:200px;
- position:relative;
- display:flex;
+ height: 200px;
+ position: relative;
+ display: flex;
}
+
.mainView-library {
- height:75%;
- position:relative;
- display:flex;
+ height: 75%;
+ position: relative;
+ display: flex;
}
+
.mainView-recentlyClosed {
- height:25%;
- position:relative;
- display:flex;
+ height: 25%;
+ position: relative;
+ display: flex;
} \ No newline at end of file
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 8e2d7be85..72eb956e3 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -12,6 +12,7 @@ import "./MainOverlayTextBox.scss";
import { FormattedTextBox } from './nodes/FormattedTextBox';
interface MainOverlayTextBoxProps {
+ firstinstance?: boolean;
}
@observer
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 88a636784..2ecf5fd85 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -383,7 +383,7 @@ export class MainView extends React.Component {
let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
// let addDockingNode = action(() => Docs.Create.StandardCollectionDockingDocument([{ doc: addColNode(), initialWidth: 200 }], { width: 200, height: 200, title: "a nested docking freeform collection" }));
- let addSchemaNode = action(() => Docs.Create.SchemaDocument([new SchemaHeaderField("title")], [], { width: 200, height: 200, title: "a schema collection" }));
+ let addSchemaNode = action(() => Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], [], { width: 200, height: 200, title: "a schema collection" }));
//let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
// let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" }));
let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
@@ -458,6 +458,7 @@ export class MainView extends React.Component {
@observable isSearchVisible = false;
@action
toggleSearch = () => {
+ // console.log("search toggling")
this.isSearchVisible = !this.isSearchVisible;
}
@@ -471,7 +472,7 @@ export class MainView extends React.Component {
{this.nodesMenu()}
{this.miscButtons}
<PDFMenu />
- <MainOverlayTextBox />
+ <MainOverlayTextBox firstinstance={true} />
<OverlayView />
</div >
);
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
new file mode 100644
index 000000000..13e4b88f7
--- /dev/null
+++ b/src/client/views/SearchItem.tsx
@@ -0,0 +1,69 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Doc } from "../../new_fields/Doc";
+import { DocumentManager } from "../util/DocumentManager";
+import { SetupDrag } from "../util/DragManager";
+
+
+export interface SearchProps {
+ doc: Doc;
+}
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+export class SearchItem extends React.Component<SearchProps> {
+
+ onClick = () => {
+ DocumentManager.Instance.jumpToDocument(this.props.doc, false);
+ }
+
+ //needs help
+ // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
+
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
+ }
+ onPointerEnter = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = true;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+ }
+ onPointerLeave = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = false;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+ }
+
+ collectionRef = React.createRef<HTMLDivElement>();
+ startDocDrag = () => {
+ let doc = this.props.doc;
+ const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
+ if (isProto) {
+ return Doc.MakeDelegate(doc);
+ } else {
+ return Doc.MakeAlias(doc);
+ }
+ }
+ render() {
+ return (
+ <div className="search-item" ref={this.collectionRef} id="result"
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+ onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
+ <div className="search-title" id="result" >title: {this.props.doc.title}</div>
+ {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
+ {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1b32f0ddd..c413650f0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -7,6 +7,10 @@ import { DocumentView } from "./nodes/DocumentView";
import { Template } from "./Templates";
import React = require("react");
import { undoBatch } from "../util/UndoManager";
+import { DocumentManager } from "../util/DocumentManager";
+import { NumCast } from "../../new_fields/Types";
+import { DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -35,11 +39,46 @@ export interface TemplateMenuProps {
@observer
export class TemplateMenu extends React.Component<TemplateMenuProps> {
@observable private _hidden: boolean = true;
+ dragRef = React.createRef<HTMLDivElement>();
constructor(props: TemplateMenuProps) {
super(props);
}
+ toggleFloat = (e: React.MouseEvent): void => {
+ SelectionManager.DeselectAll();
+ let topDocView = this.props.docs[0];
+ let topDoc = topDocView.props.Document;
+ let xf = topDocView.props.ScreenToLocalTransform();
+ let ex = e.clientX;
+ let ey = e.clientY;
+ undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
+ if (!topDoc.z) {
+ setTimeout(() => {
+ let newDocView = DocumentManager.Instance.getDocumentView(topDoc);
+ if (newDocView) {
+ let de = new DragManager.DocumentDragData([topDoc], [undefined]);
+ de.moveDocument = topDocView.props.moveDocument;
+ let xf = newDocView.ContentDiv!.getBoundingClientRect();
+ console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
+ DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
+ offsetX: (ex - xf.left), offsetY: (ey - xf.top),
+ handlers: {
+ dragComplete: () => { },
+ },
+ hideSource: false
+ });
+ }
+ }, 10);
+ } else if (topDocView.props.ContainingCollectionView) {
+ let collView = topDocView.props.ContainingCollectionView!;
+ let [sx, sy] = xf.inverse().transformPoint(0, 0);
+ let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
+ topDoc.x = x;
+ topDoc.y = y;
+ }
+ }
+
@undoBatch
@action
toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -89,9 +128,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
return (
<div className="templating-menu" >
<div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
- <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+ <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
{templateMenu}
- <button style={{ display: this._hidden ? "none" : "block" }} onClick={this.clearTemplates}>Clear</button>
+ <button onClick={this.toggleFloat}>Float</button>
+ <button onClick={this.clearTemplates}>Clear</button>
</ul>
</div>
);
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index fdf0896ac..4ff65b277 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -26,6 +26,7 @@ import { faExpand } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
import { KeyCodes } from "../../northstar/utils/KeyCodes";
+import { undoBatch } from "../../util/UndoManager";
library.add(faExpand);
@@ -71,6 +72,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
document.removeEventListener("keydown", this.onKeyDown);
this._isEditing = true;
this.props.setIsEditing(true);
+
}
}
@@ -87,11 +89,15 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
+ // this._isEditing = true;
+ // this.props.setIsEditing(true);
+
let field = this.props.rowProps.original[this.props.rowProps.column.id!];
let doc = FieldValue(Cast(field, Doc));
if (typeof field === "object" && doc) this.props.setPreviewDoc(doc);
}
+ @undoBatch
applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
if (!res.success) return false;
@@ -108,7 +114,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
this._document[fieldKey] = de.data.draggedDocuments[0];
}
else {
- let coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title")], de.data.draggedDocuments, {});
+ let coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.data.draggedDocuments, {});
this._document[fieldKey] = coll;
}
e.stopPropagation();
@@ -122,17 +128,17 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
}
- expandDoc = (e: React.PointerEvent) => {
- let field = this.props.rowProps.original[this.props.rowProps.column.id as string];
- let doc = FieldValue(Cast(field, Doc));
+ // expandDoc = (e: React.PointerEvent) => {
+ // let field = this.props.rowProps.original[this.props.rowProps.column.id as string];
+ // let doc = FieldValue(Cast(field, Doc));
- console.log("Expanding doc", StrCast(doc!.title));
- this.props.setPreviewDoc(doc!);
+ // console.log("Expanding doc", StrCast(doc!.title));
+ // this.props.setPreviewDoc(doc!);
- // this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
+ // // this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
- e.stopPropagation();
- }
+ // e.stopPropagation();
+ // }
renderCellWithType(type: string | undefined) {
let dragRef: React.RefObject<HTMLDivElement> = React.createRef();
@@ -285,7 +291,7 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
this._isChecked = e.target.checked;
let script = CompileScript(e.target.checked.toString(), { requiredType: "boolean", addReturn: true, params: { this: Doc.name } });
if (script.compiled) {
- this.applyToDoc(this._document, script.run);
+ this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
}
}
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index 387107c55..dfd65770e 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
import "./CollectionSchemaView.scss";
-import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn } from '@fortawesome/free-solid-svg-icons';
+import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes } from '@fortawesome/free-solid-svg-icons';
import { library, IconProp } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Flyout, anchorPoints } from "../DocumentDecorations";
@@ -10,9 +10,10 @@ import { ColumnType } from "./CollectionSchemaView";
import { emptyFunction } from "../../../Utils";
import { contains } from "typescript-collections/dist/lib/arrays";
import { faFile } from "@fortawesome/free-regular-svg-icons";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField, RandomPastel, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
+import { undoBatch } from "../../util/UndoManager";
-library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile);
+library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile, faSortAmountDown, faSortAmountUp, faTimes);
export interface HeaderProps {
keyValue: SchemaHeaderField;
@@ -23,23 +24,24 @@ export interface HeaderProps {
onSelect: (oldKey: string, newKey: string, addnew: boolean) => void;
setIsEditing: (isEditing: boolean) => void;
deleteColumn: (column: string) => void;
- setColumnType: (key: string, type: ColumnType) => void;
- setColumnSort: (key: string, desc: boolean) => void;
- removeColumnSort: (key: string) => void;
+ setColumnType: (column: SchemaHeaderField, type: ColumnType) => void;
+ setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void;
+ setColumnColor: (column: SchemaHeaderField, color: string) => void;
+
}
export class CollectionSchemaHeader extends React.Component<HeaderProps> {
render() {
let icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" :
this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" : "align-justify";
-
return (
<div className="collectionSchemaView-header" style={{ background: this.props.keyValue.color }}>
<CollectionSchemaColumnMenu
- keyValue={this.props.keyValue.heading}
+ columnField={this.props.keyValue}
+ // keyValue={this.props.keyValue.heading}
possibleKeys={this.props.possibleKeys}
existingKeys={this.props.existingKeys}
- keyType={this.props.keyType}
+ // keyType={this.props.keyType}
typeConst={this.props.typeConst}
menuButtonContent={<div><FontAwesomeIcon icon={icon} size="sm" />{this.props.keyValue.heading}</div>}
addNew={false}
@@ -49,7 +51,7 @@ export class CollectionSchemaHeader extends React.Component<HeaderProps> {
onlyShowOptions={false}
setColumnType={this.props.setColumnType}
setColumnSort={this.props.setColumnSort}
- removeColumnSort={this.props.removeColumnSort}
+ setColumnColor={this.props.setColumnColor}
/>
</div>
);
@@ -70,13 +72,12 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe
}
}
-
-
export interface ColumnMenuProps {
- keyValue: string;
+ columnField: SchemaHeaderField;
+ // keyValue: string;
possibleKeys: string[];
existingKeys: string[];
- keyType: ColumnType;
+ // keyType: ColumnType;
typeConst: boolean;
menuButtonContent: JSX.Element;
addNew: boolean;
@@ -84,10 +85,10 @@ export interface ColumnMenuProps {
setIsEditing: (isEditing: boolean) => void;
deleteColumn: (column: string) => void;
onlyShowOptions: boolean;
- setColumnType: (key: string, type: ColumnType) => void;
- setColumnSort: (key: string, desc: boolean) => void;
- removeColumnSort: (key: string) => void;
+ setColumnType: (column: SchemaHeaderField, type: ColumnType) => void;
+ setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void;
anchorPoint?: any;
+ setColumnColor: (column: SchemaHeaderField, color: string) => void;
}
@observer
export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> {
@@ -116,10 +117,16 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
this.props.setIsEditing(this._isOpen);
}
- setColumnType = (oldKey: string, newKey: string, addnew: boolean) => {
- let typeStr = newKey as keyof typeof ColumnType;
- let type = ColumnType[typeStr];
- this.props.setColumnType(this.props.keyValue, type);
+ changeColumnType = (type: ColumnType): void => {
+ this.props.setColumnType(this.props.columnField, type);
+ }
+
+ changeColumnSort = (desc: boolean | undefined): void => {
+ this.props.setColumnSort(this.props.columnField, desc);
+ }
+
+ changeColumnColor = (color: string): void => {
+ this.props.setColumnColor(this.props.columnField, color);
}
@action
@@ -129,78 +136,82 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
}
}
- changeColumnColor = (color: string): void => {
-
- }
-
renderTypes = () => {
if (this.props.typeConst) return <></>;
+
+ let type = this.props.columnField.type;
return (
<div className="collectionSchema-headerMenu-group">
<label>Column type:</label>
<div className="columnMenu-types">
- <button title="Any" className={this.props.keyType === ColumnType.Any ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Any)}>
+ <div className={"columnMenu-option" + (type === ColumnType.Any ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Any)}>
<FontAwesomeIcon icon={"align-justify"} size="sm" />
- </button>
- <button title="Number" className={this.props.keyType === ColumnType.Number ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Number)}>
+ Any
+ </div>
+ <div className={"columnMenu-option" + (type === ColumnType.Number ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Number)}>
<FontAwesomeIcon icon={"hashtag"} size="sm" />
- </button>
- <button title="String" className={this.props.keyType === ColumnType.String ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.String)}>
+ Number
+ </div>
+ <div className={"columnMenu-option" + (type === ColumnType.String ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.String)}>
<FontAwesomeIcon icon={"font"} size="sm" />
- </button>
- <button title="Checkbox" className={this.props.keyType === ColumnType.Boolean ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Boolean)}>
+ Text
+ </div>
+ <div className={"columnMenu-option" + (type === ColumnType.Boolean ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Boolean)}>
<FontAwesomeIcon icon={"check-square"} size="sm" />
- </button>
- <button title="Document" className={this.props.keyType === ColumnType.Doc ? "active" : ""} onClick={() => this.props.setColumnType(this.props.keyValue, ColumnType.Doc)}>
+ Checkbox
+ </div>
+ <div className={"columnMenu-option" + (type === ColumnType.Doc ? " active" : "")} onClick={() => this.changeColumnType(ColumnType.Doc)}>
<FontAwesomeIcon icon={"file"} size="sm" />
- </button>
+ Document
+ </div>
</div>
- </div>
+ </div >
);
}
renderSorting = () => {
+ let sort = this.props.columnField.desc;
return (
<div className="collectionSchema-headerMenu-group">
<label>Sort by:</label>
<div className="columnMenu-sort">
- <div className="columnMenu-option" onClick={() => this.props.setColumnSort(this.props.keyValue, false)}>Sort ascending</div>
- <div className="columnMenu-option" onClick={() => this.props.setColumnSort(this.props.keyValue, true)}>Sort descending</div>
- <div className="columnMenu-option" onClick={() => this.props.removeColumnSort(this.props.keyValue)}>Clear sorting</div>
+ <div className={"columnMenu-option" + (sort === true ? " active" : "")} onClick={() => this.changeColumnSort(true)}>
+ <FontAwesomeIcon icon="sort-amount-down" size="sm" />
+ Sort descending
+ </div>
+ <div className={"columnMenu-option" + (sort === false ? " active" : "")} onClick={() => this.changeColumnSort(false)}>
+ <FontAwesomeIcon icon="sort-amount-up" size="sm" />
+ Sort ascending
+ </div>
+ <div className="columnMenu-option" onClick={() => this.changeColumnSort(undefined)}>
+ <FontAwesomeIcon icon="times" size="sm" />
+ Clear sorting
+ </div>
</div>
</div>
);
}
renderColors = () => {
+ let selected = this.props.columnField.color;
+
+ let pink = PastelSchemaPalette.get("pink2");
+ let purple = PastelSchemaPalette.get("purple2");
+ let blue = PastelSchemaPalette.get("bluegreen1");
+ let yellow = PastelSchemaPalette.get("yellow4");
+ let red = PastelSchemaPalette.get("red2");
+ let gray = "#f1efeb";
+
return (
<div className="collectionSchema-headerMenu-group">
<label>Color:</label>
<div className="columnMenu-colors">
- <input type="radio" name="column-color" id="pink" value="#FFB4E8" onClick={() => this.changeColumnColor("#FFB4E8")} />
- <label htmlFor="pink">
- <div className="columnMenu-colorPicker" style={{ backgroundColor: "#FFB4E8" }}></div>
- </label>
- <input type="radio" name="column-color" id="purple" value="#b28dff" onClick={() => this.changeColumnColor("#b28dff")} />
- <label htmlFor="purple">
- <div className="columnMenu-colorPicker" style={{ backgroundColor: "#FFB4E8" }}></div>
- </label>
- <input type="radio" name="column-color" id="blue" value="#afcbff" onClick={() => this.changeColumnColor("#afcbff")} />
- <label htmlFor="blue">
- <div className="columnMenu-colorPicker" style={{ backgroundColor: "#FFB4E8" }}></div>
- </label>
- <input type="radio" name="column-color" id="yellow" value="#f3ffe3" onClick={() => this.changeColumnColor("#f3ffe3")} />
- <label htmlFor="yellow">
- <div className="columnMenu-colorPicker" style={{ backgroundColor: "#FFB4E8" }}></div>
- </label>
- <input type="radio" name="column-color" id="red" value="#ffc9de" onClick={() => this.changeColumnColor("#ffc9de")} />
- <label htmlFor="red">
- <div className="columnMenu-colorPicker" style={{ backgroundColor: "#FFB4E8" }}></div>
- </label>
- <input type="radio" name="column=color" id="none" value="#f1efeb" onClick={() => this.changeColumnColor("#f1efeb")} />
- <label htmlFor="none">
- <div className="columnMenu-colorPicker" style={{ backgroundColor: "#FFB4E8" }}></div>
- </label>
+ <div className={"columnMenu-colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
+ <div className={"columnMenu-colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
</div>
</div>
);
@@ -209,10 +220,10 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
renderContent = () => {
return (
<div className="collectionSchema-header-menuOptions">
- <label>Key:</label>
<div className="collectionSchema-headerMenu-group">
+ <label>Key:</label>
<KeysDropdown
- keyValue={this.props.keyValue}
+ keyValue={this.props.columnField.heading}
possibleKeys={this.props.possibleKeys}
existingKeys={this.props.existingKeys}
canAddNew={true}
@@ -227,7 +238,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps>
{this.renderSorting()}
{this.renderColors()}
<div className="collectionSchema-headerMenu-group">
- <button onClick={() => this.props.deleteColumn(this.props.keyValue)}>Delete Column</button>
+ <button onClick={() => this.props.deleteColumn(this.props.columnField.heading)}>Delete Column</button>
</div>
</>
}
@@ -259,9 +270,10 @@ interface KeysDropdownProps {
@observer
class KeysDropdown extends React.Component<KeysDropdownProps> {
@observable private _key: string = this.props.keyValue;
- @observable private _searchTerm: string = "";
+ @observable private _searchTerm: string = this.props.keyValue;
@observable private _isOpen: boolean = false;
@observable private _canClose: boolean = true;
+ @observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef();
@action setSearchTerm = (value: string): void => { this._searchTerm = value; };
@action setKey = (key: string): void => { this._key = key; };
@@ -275,6 +287,22 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
this.props.setIsEditing(false);
}
+ @undoBatch
+ @action
+ onKeyDown = (e: React.KeyboardEvent): void => {
+ if (e.key === "Enter") {
+ let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1);
+ let exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 ||
+ this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1;
+
+ if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) {
+ this.onSelect(this._searchTerm);
+ } else {
+ this._searchTerm = this._key;
+ }
+ }
+ }
+
onChange = (val: string): void => {
this.setSearchTerm(val);
}
@@ -327,7 +355,7 @@ class KeysDropdown extends React.Component<KeysDropdownProps> {
render() {
return (
<div className="keys-dropdown">
- <input className="keys-search" type="text" value={this._searchTerm} placeholder="Search for or create a new key"
+ <input className="keys-search" ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown}
onChange={e => this.onChange(e.target.value)} onFocus={this.onFocus} onBlur={this.onBlur}></input>
<div className="keys-options-wrapper" onPointerEnter={this.onPointerEnter} onPointerOut={this.onPointerOut}>
{this.renderOptions()}
diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
index 7342ede7a..ec40043cc 100644
--- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
+++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx
@@ -13,6 +13,7 @@ import { faGripVertical, faTrash } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { DocumentManager } from "../../util/DocumentManager";
import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { undoBatch } from "../../util/UndoManager";
library.add(faGripVertical, faTrash);
@@ -26,6 +27,9 @@ export interface MovableColumnProps {
export class MovableColumn extends React.Component<MovableColumnProps> {
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _colDropDisposer?: DragManager.DragDropDisposer;
+ private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
+ private _sensitivity: number = 16;
+ private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
onPointerEnter = (e: React.PointerEvent): void => {
if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
@@ -36,6 +40,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
onPointerLeave = (e: React.PointerEvent): void => {
this._header!.current!.className = "collectionSchema-col-wrapper";
document.removeEventListener("pointermove", this.onDragMove, true);
+ document.removeEventListener("pointermove", this.onPointerMove);
}
onDragMove = (e: PointerEvent): void => {
let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
@@ -68,7 +73,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
return false;
}
- setupDrag(ref: React.RefObject<HTMLElement>) {
+ onPointerMove = (e: PointerEvent) => {
let onRowMove = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
@@ -76,35 +81,44 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
let dragData = new DragManager.ColumnDragData(this.props.columnValue);
- DragManager.StartColumnDrag(ref.current!, dragData, e.x, e.y);
+ DragManager.StartColumnDrag(this._dragRef.current!, dragData, e.x, e.y);
};
let onRowUp = (): void => {
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
- let onItemDown = (e: React.PointerEvent) => {
- if (e.button === 0) {
+ if (e.buttons === 1) {
+ let [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
+ if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+ document.removeEventListener("pointermove", this.onPointerMove);
e.stopPropagation();
+
document.addEventListener("pointermove", onRowMove);
document.addEventListener("pointerup", onRowUp);
}
- };
- return onItemDown;
+ }
}
- // onColDrag = (e: React.DragEvent, ref: React.RefObject<HTMLDivElement>) => {
- // this.setupDrag(reference);
- // }
+ onPointerUp = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ }
+
+ @action
+ onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
+ this._dragRef = ref;
+ let [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
+ this._startDragPosition = { x: dx, y: dy };
+ document.addEventListener("pointermove", this.onPointerMove);
+ }
render() {
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = this.setupDrag(reference);
return (
<div className="collectionSchema-col" ref={this.createColDropTarget}>
<div className="collectionSchema-col-wrapper" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- <div className="col-dragger" ref={reference} onPointerDown={onItemDown} >
+ <div className="col-dragger" ref={reference} onPointerDown={e => this.onPointerDown(e, reference)} onPointerUp={this.onPointerUp}>
{this.props.columnRenderer}
</div>
</div>
@@ -183,6 +197,7 @@ export class MovableRow extends React.Component<MovableRowProps> {
ContextMenu.Instance.addItem({ description: description, event: () => this.props.textWrapRow(this.props.rowInfo.original), icon: "file-pdf" });
}
+ @undoBatch
@action
move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
let targetView = DocumentManager.Instance.getDocumentView(target);
@@ -212,7 +227,6 @@ export class MovableRow extends React.Component<MovableRowProps> {
let className = "collectionSchema-row";
if (this.props.rowFocused) className += " row-focused";
if (this.props.rowWrapped) className += " row-wrapped";
- // if (!this.props.rowWrapped) className += " row-unwrapped";
return (
<div className={className} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}>
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index b1e98b162..01744fb34 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -9,26 +9,22 @@
position: absolute;
top: 0;
width: 100%;
- transition: height .5s;
height: 100%;
- // overflow: hidden;
- // overflow-x: scroll;
- // border: none;
- overflow: hidden;
+ transition: top 0.5s;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: nowrap;
- // .collectionSchemaView-cellContents {
- // height: $MAX_ROW_HEIGHT;
- // img {
- // width: auto;
- // max-height: $MAX_ROW_HEIGHT;
- // }
- // }
+ .collectionSchemaView-tableContainer {
+ width: 100%;
+ height: 100%;
+ overflow: scroll;
+ }
.collectionSchemaView-previewRegion {
position: relative;
background: $light-color;
- float: left;
height: 100%;
.collectionSchemaView-previewDoc {
@@ -52,7 +48,6 @@
.collectionSchemaView-dividerDragger {
position: relative;
- float: left;
height: 100%;
width: 20px;
z-index: 20;
@@ -60,50 +55,35 @@
top: 0;
background: gray;
cursor: col-resize;
- // background: $main-accent;
- // box-sizing: border-box;
- // border-left: 1px solid $intermediate-color;
- // border-right: 1px solid $intermediate-color;
+ }
+
+ .documentView-node:first-child {
+ background: $light-color;
}
}
.ReactTable {
width: 100%;
- height: 100%;
background: white;
box-sizing: border-box;
border: none !important;
+ float: none !important;
.rt-table {
- overflow-y: auto;
- overflow-x: auto;
height: 100%;
display: -webkit-inline-box;
- direction: ltr;
+ direction: ltr;
+ overflow: visible;
}
.rt-thead {
- width: calc(100% - 50px);
+ width: calc(100% - 52px);
margin-left: 50px;
&.-header {
- // background: $intermediate-color;
- // color: $light-color;
font-size: 12px;
height: 30px;
- // border: 1px solid $intermediate-color;
box-shadow: none;
- // width: calc(100% - 30px);
- // margin-right: -30px;
- }
-
- .rt-resizable-header {
- padding: 0;
- height: 30px;
-
- &:last-child {
- overflow: visible;
- }
}
.rt-resizable-header-content {
@@ -115,14 +95,13 @@
padding: 0;
border: solid lightgray;
border-width: 0 1px;
+ border-bottom: 2px solid lightgray;
}
}
.rt-th {
- // max-height: $MAX_ROW_HEIGHT;
font-size: 13px;
text-align: center;
- background-color: $light-color-secondary;
&:last-child {
overflow: visible;
@@ -130,6 +109,7 @@
}
.rt-tbody {
+ width: calc(100% - 2px);
direction: rtl;
overflow: visible;
}
@@ -139,45 +119,18 @@
flex: 0 1 auto;
min-height: 30px;
border: 0 !important;
- // border: solid lightgray;
- // border-width: 1px 0;
- // border-left: 1px solid lightgray;
- // max-height: $MAX_ROW_HEIGHT;
- // for sub comp
-
- // &:nth-child(even) {
- // background-color: $light-color;
- // }
-
- // &:nth-child(odd) {
- // background-color: $light-color-secondary;
- // }
-
- // &:first-child {
- // border-top: 1px solid $light-color-secondary !important;
- // }
- // &:last-child {
- // border-bottom: 1px solid $light-color-secondary !important;
- // }
}
.rt-tr {
width: 100%;
min-height: 30px;
- // height: $MAX_ROW_HEIGHT;
}
.rt-td {
- // border: 1px solid $light-color-secondary !important;
- // border-width: 0 1px;
- // border-width: 1px;
- // border-right-color: $intermediate-color;
- // max-height: $MAX_ROW_HEIGHT;
padding: 0;
font-size: 13px;
text-align: center;
-
- // white-space: normal;
+ white-space: nowrap;
.imageBox-cont {
position: relative;
@@ -198,8 +151,21 @@
}
.rt-resizer {
- width: 20px;
- right: -10px;
+ width: 8px;
+ right: -4px;
+ }
+
+ .rt-resizable-header {
+ padding: 0;
+ height: 30px;
+ }
+
+ .rt-resizable-header:last-child {
+ overflow: visible;
+
+ .rt-resizer {
+ width: 5px !important;
+ }
}
}
@@ -209,10 +175,6 @@
display: inline-block;
}
-.documentView-node:first-child {
- background: $light-color;
-}
-
.collectionSchema-col {
height: 100%;
@@ -246,11 +208,6 @@
margin-right: 4px;
}
}
-
- // div[class*="css"] {
- // width: 100%;
- // height: 100%;
- // }
}
}
@@ -260,16 +217,29 @@ button.add-column {
.collectionSchema-header-menuOptions {
color: black;
- width: 175px;
+ width: 200px;
text-align: left;
.collectionSchema-headerMenu-group {
- margin-bottom: 10px;
+ padding: 7px 0;
+ border-bottom: 1px solid lightgray;
+
+ &:first-child {
+ padding-top : 0;
+ }
+
+ &:last-child {
+ border: none;
+ text-align: center;
+ padding: 12px 0 0 0;
+ }
}
label {
color: $main-accent;
font-weight: normal;
+ letter-spacing: 2px;
+ text-transform: uppercase;
}
input {
@@ -277,23 +247,57 @@ button.add-column {
width: 100%;
}
+ .columnMenu-option {
+ cursor: pointer;
+ padding: 3px;
+ background-color: white;
+ transition: background-color 0.2s;
+
+ &:hover {
+ background-color: $light-color-secondary;
+ }
+
+ &.active {
+ font-weight: bold;
+ border: 2px solid $light-color-secondary;
+ }
+
+ svg {
+ color: gray;
+ margin-right: 5px;
+ width: 10px;
+ }
+ }
+
.keys-dropdown {
position: relative;
- max-width: 175px;
+ width: 100%;
+
+ input {
+ border: 2px solid $light-color-secondary;
+ padding: 3px;
+ height: 28px;
+ font-weight: bold;
+
+ &:focus {
+ font-weight: normal;
+ }
+ }
.keys-options-wrapper {
width: 100%;
max-height: 150px;
overflow-y: scroll;
position: absolute;
- top: 20px;
+ top: 28px;
+ box-shadow: 0 10px 16px rgba(0,0,0,0.1);
.key-option {
background-color: $light-color;
- border: 1px solid $light-color-secondary;
+ border: 1px solid lightgray;
padding: 2px 3px;
- &:not(:last-child) {
+ &:not(:first-child) {
border-top: 0;
}
@@ -304,60 +308,51 @@ button.add-column {
}
}
- .columnMenu-types {
+ .columnMenu-colors {
display: flex;
justify-content: space-between;
-
- button {
- border-radius: 20px;
- }
- }
-
- .columnMenu-colors {
-
-
- input[type="radio"] {
- display: none;
- }
+ flex-wrap: wrap;
.columnMenu-colorPicker {
+ cursor: pointer;
width: 20px;
height: 20px;
+ border-radius: 10px;
+
+ &.active {
+ border: 2px solid white;
+ box-shadow: 0 0 0 2px lightgray;
+ }
}
}
}
.collectionSchema-row {
- // height: $MAX_ROW_HEIGHT;
height: 100%;
background-color: white;
- &.row-focused .rt-tr {
+ &.row-focused .rt-td {
background-color: rgb(255, 246, 246); //$light-color-secondary;
}
&.row-wrapped {
- white-space: normal;
+ .rt-td {
+ white-space: normal;
+ }
}
.row-dragger {
display: flex;
justify-content: space-around;
- // height: $MAX_ROW_HEIGHT;
flex: 50 0 auto;
width: 50px;
max-width: 50px;
height: 100%;
min-height: 30px;
- // padding: 5px 5px 5px 0;
color: lightgray;
background-color: white;
transition: color 0.1s ease;
- // &:hover {
- // color: lightgray;
- // }
-
.row-option {
// padding: 5px;
cursor: pointer;
@@ -373,7 +368,6 @@ button.add-column {
}
.collectionSchema-row-wrapper {
- // max-height: $MAX_ROW_HEIGHT;
&.row-above {
border-top: 1px solid red;
@@ -407,18 +401,22 @@ button.add-column {
outline: none;
}
- &.focused {
- // background-color: yellowgreen;
- // border: 2px solid yellowgreen;
-
+ &.editing {
+ padding: 0;
input {
outline: 0;
border: none;
- background-color: yellow;
+ background-color: rgb(255, 217, 217);
+ width: 100%;
+ height: 100%;
+ padding: 2px 3px;
+ min-height: 26px;
}
+ }
+
+ &.focused {
&.inactive {
- // border: 2px solid rgba(255, 255, 0, 0.4);
border: none;
}
}
@@ -426,7 +424,6 @@ button.add-column {
p {
width: 100%;
height: 100%;
- // word-wrap: break-word;
}
&:hover .collectionSchemaView-cellContents-docExpander {
@@ -453,9 +450,7 @@ button.add-column {
display: flex;
justify-content: flex-end;
padding: 0 10px;
-
border-bottom: 2px solid gray;
- // margin-bottom: 10px;
.collectionSchemaView-toolbar-item {
display: flex;
@@ -470,23 +465,17 @@ button.add-column {
}
.collectionSchemaView-table {
- width: calc(100% - 7px);
+ width: 100%;
+ height: 100%;
+ overflow: visible;
}
.sub {
padding: 10px 30px;
- // padding-left: 80px;
background-color: rgb(252, 252, 252);
width: calc(100% - 50px);
margin-left: 50px;
- .rt-table {
- overflow-x: hidden; // todo; this shouldnt be like this :((
- overflow-y: visible;
- }
-
- // TODO fix
-
.row-dragger {
background-color: rgb(252, 252, 252);
}
@@ -502,4 +491,25 @@ button.add-column {
.collectionSchemaView-expander {
height: 100%;
+ min-height: 30px;
+ position: relative;
+ color: gray;
+
+ svg {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+}
+
+.collectionSchemaView-addRow {
+ color: gray;
+ letter-spacing: 2px;
+ 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 08ab22725..9efd0d3ec 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -4,10 +4,10 @@ import { faCog, faPlus, faTable, faSortUp, faSortDown } from '@fortawesome/free-
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
-import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults, TableCellRenderer, Column, RowInfo } from "react-table";
+import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize } from "react-table";
import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
-import { Doc, DocListCast, DocListCastAsync, Field, FieldResult, Opt } from "../../../new_fields/Doc";
+import { emptyFunction, returnOne } from "../../../Utils";
+import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
@@ -17,28 +17,21 @@ import { Gateway } from "../../northstar/manager/Gateway";
import { SetupDrag, DragManager } from "../../util/DragManager";
import { CompileScript, ts, Transformer } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
-import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
+import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss';
import { ContextMenu } from "../ContextMenu";
-import { anchorPoints, Flyout } from "../DocumentDecorations";
import '../DocumentDecorations.scss';
-import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
-import { FieldView, FieldViewProps } from "../nodes/FieldView";
import { CollectionPDFView } from "./CollectionPDFView";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
import { undoBatch } from "../../util/UndoManager";
-import { timesSeries } from "async";
import { CollectionSchemaHeader, CollectionSchemaAddColumnHeader } from "./CollectionSchemaHeaders";
import { CellProps, CollectionSchemaCell, CollectionSchemaNumberCell, CollectionSchemaStringCell, CollectionSchemaBooleanCell, CollectionSchemaCheckboxCell, CollectionSchemaDocCell } from "./CollectionSchemaCells";
import { MovableColumn, MovableRow } from "./CollectionSchemaMovableTableHOC";
-import { SelectionManager } from "../../util/SelectionManager";
-import { DocumentManager } from "../../util/DocumentManager";
-import { ImageBox } from "../nodes/ImageBox";
import { ComputedField } from "../../../new_fields/ScriptField";
-import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
library.add(faCog, faPlus, faSortUp, faSortDown);
@@ -51,7 +44,6 @@ export enum ColumnType {
String,
Boolean,
Doc,
- // Checkbox
}
// this map should be used for keys that should have a const type of value
const columnTypes: Map<string, ColumnType> = new Map([
@@ -82,14 +74,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
super.CreateDropTarget(ele);
}
- // detectClick = (e: PointerEvent): void => {
- // if (this._node && this._node.contains(e.target as Node)) {
- // } else {
- // this._isOpen = false;
- // this.props.setIsEditing(false);
- // }
- // }
-
isFocused = (doc: Doc): boolean => {
if (!this.props.isSelected()) return false;
return doc === this._focusedTable;
@@ -121,8 +105,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont!.getBoundingClientRect();
- this.props.Document.schemaPreviewWidth = Math.min(nativeWidth.right - nativeWidth.left - 40,
- this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]);
+ let minWidth = 40;
+ let maxWidth = 1000;
+ let movedWidth = this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0];
+ let width = movedWidth < minWidth ? minWidth : movedWidth > maxWidth ? maxWidth : movedWidth;
+ this.props.Document.schemaPreviewWidth = width;
}
@action
onDividerUp = (e: PointerEvent): void => {
@@ -189,6 +176,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
/>
</div>;
}
+
+ @undoBatch
@action
setPreviewScript = (script: string) => {
this.previewScript = script;
@@ -234,12 +223,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
render() {
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
- // if (SelectionManager.SelectedDocuments().length > 0) console.log(StrCast(SelectionManager.SelectedDocuments()[0].Document.title));
- // if (DocumentManager.Instance.getDocumentView(this.props.Document)) console.log(StrCast(this.props.Document.title), SelectionManager.IsSelected(DocumentManager.Instance.getDocumentView(this.props.Document)!))
return (
- <div className="collectionSchemaView-container"
- onPointerDown={this.onPointerDown} onWheel={this.onWheel} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
- {this.schemaTable}
+ <div className="collectionSchemaView-container" style={{ height: "100%", marginTop: "0", }}>
+ <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={this.onWheel} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
+ {this.schemaTable}
+ </div>
{this.dividerDragger}
{!this.previewWidth() ? (null) : this.previewPanel}
</div>
@@ -260,7 +248,6 @@ export interface SchemaTableProps {
deleteDocument: (document: Doc) => boolean;
moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
- // CreateDropTarget: (ele: HTMLDivElement)=> void; // super createdriotarget
active: () => boolean;
onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void;
addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
@@ -272,24 +259,24 @@ export interface SchemaTableProps {
@observer
export class SchemaTable extends React.Component<SchemaTableProps> {
- // private _mainCont?: HTMLDivElement;
private DIVIDER_WIDTH = 4;
@observable _headerIsEditing: boolean = false;
@observable _cellIsEditing: boolean = false;
@observable _focusedCell: { row: number, col: number } = { row: 0, col: 0 };
- @observable _sortedColumns: Map<string, { id: string, desc: boolean }> = new Map();
@observable _openCollections: Array<string> = [];
- @observable _textWrappedRows: Array<string> = [];
- @observable private _node: HTMLDivElement | null = null;
@computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
@computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
@computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
+
@computed get columns() {
- console.log("columns");
return Cast(this.props.Document.schemaColumns, listSpec(SchemaHeaderField), []);
}
+ set columns(columns: SchemaHeaderField[]) {
+ this.props.Document.schemaColumns = new List<SchemaHeaderField>(columns);
+ }
+
@computed get childDocs() {
if (this.props.childDocs) return this.props.childDocs;
@@ -300,7 +287,32 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
doc[this.props.fieldKey] = new List<Doc>(docs);
}
- set columns(columns: SchemaHeaderField[]) { this.props.Document.schemaColumns = new List<SchemaHeaderField>(columns); }
+
+ @computed get textWrappedRows() {
+ return Cast(this.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ }
+ set textWrappedRows(textWrappedRows: string[]) {
+ this.props.Document.textwrappedSchemaRows = new List<string>(textWrappedRows);
+ }
+
+ @computed get resized(): { "id": string, "value": number }[] {
+ return this.columns.reduce((resized, shf) => {
+ if (shf.width > -1) {
+ resized.push({ "id": shf.heading, "value": shf.width });
+ }
+ return resized;
+ }, [] as { "id": string, "value": number }[]);
+ }
+
+ @computed get sorted(): { "id": string, "desc": boolean }[] {
+ return this.columns.reduce((sorted, shf) => {
+ if (shf.desc) {
+ sorted.push({ "id": shf.heading, "desc": shf.desc });
+ }
+ return sorted;
+ }, [] as { "id": string, "desc": boolean }[]);
+ }
+
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@computed get tableColumns(): Column<Doc>[] {
let possibleKeys = this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1);
@@ -310,8 +322,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
let focusedCol = this._focusedCell.col;
let isEditable = !this._headerIsEditing;// && this.props.isSelected();
- // let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = DocListCast(cdoc[this.props.fieldKey]);
let children = this.childDocs;
if (children.reduce((found, doc) => found || doc.type === "collection", false)) {
@@ -344,7 +354,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
deleteColumn={this.deleteColumn}
setColumnType={this.setColumnType}
setColumnSort={this.setColumnSort}
- removeColumnSort={this.removeColumnSort}
+ setColumnColor={this.setColumnColor}
/>;
return {
@@ -399,25 +409,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return columns;
}
- // onHeaderDrag = (columnName: string) => {
- // let schemaDoc = Cast(this.props.Document.schemaDoc, Doc);
- // if (schemaDoc instanceof Doc) {
- // let columnDocs = DocListCast(schemaDoc.data);
- // if (columnDocs) {
- // let ddoc = columnDocs.find(doc => doc.title === columnName);
- // if (ddoc) {
- // return ddoc;
- // }
- // }
- // }
- // return this.props.Document;
- // }
constructor(props: SchemaTableProps) {
super(props);
// convert old schema columns (list of strings) into new schema columns (list of schema header fields)
let oldSchemaColumns = Cast(this.props.Document.schemaColumns, listSpec("string"), []);
if (oldSchemaColumns && oldSchemaColumns.length && typeof oldSchemaColumns[0] !== "object") {
- console.log("REMAKING COLUMNs");
let newSchemaColumns = oldSchemaColumns.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i);
this.props.Document.schemaColumns = new List<SchemaHeaderField>(newSchemaColumns);
}
@@ -436,8 +432,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
tableRemoveDoc = (document: Doc): boolean => {
- // let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []);
+
let children = this.childDocs;
if (children.indexOf(document) !== -1) {
children.splice(children.indexOf(document), 1);
@@ -456,11 +451,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
ScreenToLocalTransform: this.props.ScreenToLocalTransform,
addDoc: this.tableAddDoc,
removeDoc: this.tableRemoveDoc,
- // removeDoc: this.props.deleteDocument,
rowInfo,
rowFocused: !this._headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document),
- textWrapRow: this.textWrapRow,
- rowWrapped: this._textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1
+ textWrapRow: this.toggleTextWrapRow,
+ rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1
};
}
@@ -471,9 +465,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
let row = rowInfo.index;
//@ts-ignore
let col = this.columns.map(c => c.heading).indexOf(column!.id);
- // let col = column ? this.columns.indexOf(column!) : -1;
let isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document);
- // let column = this.columns.indexOf(column.id!);
+ let isEditing = this.props.isFocused(this.props.Document) && this._cellIsEditing;
+ // TODO: editing border doesn't work :(
return {
style: {
border: !this._headerIsEditing && isFocused ? "2px solid rgb(255, 160, 160)" : "1px solid #f1efeb"
@@ -481,19 +475,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
};
}
- // private createTarget = (ele: HTMLDivElement) => {
- // this._mainCont = ele;
- // this.props.CreateDropTarget(ele);
- // }
-
- // detectClick = (e: PointerEvent): void => {
- // if (this._node && this._node.contains(e.target as Node)) {
- // } else {
- // this._isOpen = false;
- // this.props.setIsEditing(false);
- // }
- // }
-
@action
onExpandCollection = (collection: Doc): void => {
this._openCollections.push(collection[Id]);
@@ -533,8 +514,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
let direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : "";
this.changeFocusedCellByDirection(direction);
- // let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []);
let children = this.childDocs;
const pdoc = FieldValue(children[this._focusedCell.row]);
pdoc && this.props.setPreviewDoc(pdoc);
@@ -543,8 +522,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@action
changeFocusedCellByDirection = (direction: string): void => {
- // let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []);
let children = this.childDocs;
switch (direction) {
case "tab":
@@ -569,81 +546,77 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
this._focusedCell = { row: this._focusedCell.row + 1 === children.length ? this._focusedCell.row : this._focusedCell.row + 1, col: this._focusedCell.col };
break;
}
- // const pdoc = FieldValue(children[this._focusedCell.row]);
- // pdoc && this.props.setPreviewDoc(pdoc);
}
@action
changeFocusedCellByIndex = (row: number, col: number): void => {
- // let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []);
-
this._focusedCell = { row: row, col: col };
this.props.setFocused(this.props.Document);
-
- // const fdoc = FieldValue(children[this._focusedCell.row]);
- // fdoc && this.props.setPreviewDoc(fdoc);
}
+ @undoBatch
createRow = () => {
- // let doc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = Cast(doc[this.props.fieldKey], listSpec(Doc), []);
let children = this.childDocs;
let newDoc = Docs.Create.TextDocument({ width: 100, height: 30 });
let proto = Doc.GetProto(newDoc);
proto.title = "";
children.push(newDoc);
+
this.childDocs = children;
}
+ @undoBatch
@action
createColumn = () => {
let index = 0;
- let found = this.columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
+ let columns = this.columns;
+ let found = columns.findIndex(col => col.heading.toUpperCase() === "New field".toUpperCase()) > -1;
if (!found) {
- console.log("create column found");
- this.columns.push(new SchemaHeaderField("New field", "#f1efeb"));
+ columns.push(new SchemaHeaderField("New field", "#f1efeb"));
+ this.columns = columns;
return;
}
while (found) {
index++;
- found = this.columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
+ found = columns.findIndex(col => col.heading.toUpperCase() === ("New field (" + index + ")").toUpperCase()) > -1;
}
- console.log("create column new");
- this.columns.push(new SchemaHeaderField("New field (" + index + ")", "#f1efeb"));
+ columns.push(new SchemaHeaderField("New field (" + index + ")", "#f1efeb"));
+ this.columns = columns;
}
+ @undoBatch
@action
deleteColumn = (key: string) => {
- console.log("deleting columnnn");
- let list = Cast(this.props.Document.schemaColumns, listSpec(SchemaHeaderField));
- if (list === undefined) {
- console.log("delete column");
- this.props.Document.schemaColumns = list = new List<SchemaHeaderField>([]);
+ let columns = this.columns;
+ if (columns === undefined) {
+ this.columns = new List<SchemaHeaderField>([]);
} else {
- const index = list.map(c => c.heading).indexOf(key);
+ const index = columns.map(c => c.heading).indexOf(key);
if (index > -1) {
- list.splice(index, 1);
+ columns.splice(index, 1);
+ this.columns = columns;
}
}
}
+ @undoBatch
@action
changeColumns = (oldKey: string, newKey: string, addNew: boolean) => {
- console.log("changingin columnsdfhs");
- let list = Cast(this.props.Document.schemaColumns, listSpec(SchemaHeaderField));
- if (list === undefined) {
- console.log("change columns new");
- this.props.Document.schemaColumns = list = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
+ let columns = this.columns;
+ if (columns === undefined) {
+ this.columns = new List<SchemaHeaderField>([new SchemaHeaderField(newKey, "f1efeb")]);
} else {
- console.log("change column");
if (addNew) {
- this.columns.push(new SchemaHeaderField(newKey, "f1efeb"));
+ columns.push(new SchemaHeaderField(newKey, "f1efeb"));
+ this.columns = columns;
} else {
- const index = list.map(c => c.heading).indexOf(oldKey);
+ const index = columns.map(c => c.heading).indexOf(oldKey);
if (index > -1) {
- list[index] = new SchemaHeaderField(newKey, "f1efeb");
+ let column = columns[index];
+ column.setHeading(newKey);
+ columns[index] = column;
+ this.columns = columns;
}
}
}
@@ -667,16 +640,37 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
return NumCast(typesDoc[column.heading]);
}
- setColumnType = (key: string, type: ColumnType): void => {
- if (columnTypes.get(key)) return;
- const typesDoc = FieldValue(Cast(this.props.Document.schemaColumnTypes, Doc));
- if (!typesDoc) {
- let newTypesDoc = new Doc();
- newTypesDoc[key] = type;
- this.props.Document.schemaColumnTypes = newTypesDoc;
- return;
- } else {
- typesDoc[key] = type;
+ @undoBatch
+ setColumnType = (columnField: SchemaHeaderField, type: ColumnType): void => {
+ if (columnTypes.get(columnField.heading)) return;
+
+ let columns = this.columns;
+ let index = columns.indexOf(columnField);
+ if (index > -1) {
+ columnField.setType(NumCast(type));
+ columns[index] = columnField;
+ this.columns = columns;
+ }
+
+ // const typesDoc = FieldValue(Cast(this.props.Document.schemaColumnTypes, Doc));
+ // if (!typesDoc) {
+ // let newTypesDoc = new Doc();
+ // newTypesDoc[key] = type;
+ // this.props.Document.schemaColumnTypes = newTypesDoc;
+ // return;
+ // } else {
+ // typesDoc[key] = type;
+ // }
+ }
+
+ @undoBatch
+ setColumnColor = (columnField: SchemaHeaderField, color: string): void => {
+ let columns = this.columns;
+ let index = columns.indexOf(columnField);
+ if (index > -1) {
+ columnField.setColor(color);
+ columns[index] = columnField;
+ this.columns = columns; // need to set the columns to trigger rerender
}
}
@@ -685,6 +679,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
this.columns = columns;
}
+ @undoBatch
reorderColumns = (toMove: SchemaHeaderField, relativeTo: SchemaHeaderField, before: boolean, columnsValues: SchemaHeaderField[]) => {
let columns = [...columnsValues];
let oldIndex = columns.indexOf(toMove);
@@ -694,23 +689,22 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
if (oldIndex === newIndex) return;
columns.splice(newIndex, 0, columns.splice(oldIndex, 1)[0]);
- this.setColumns(columns);
- }
-
- @action
- setColumnSort = (column: string, descending: boolean) => {
- this._sortedColumns.set(column, { id: column, desc: descending });
+ this.columns = columns;
}
+ @undoBatch
@action
- removeColumnSort = (column: string) => {
- this._sortedColumns.delete(column);
+ setColumnSort = (columnField: SchemaHeaderField, descending: boolean | undefined) => {
+ let columns = this.columns;
+ let index = columns.findIndex(c => c.heading === columnField.heading);
+ let column = columns[index];
+ column.setDesc(descending);
+ columns[index] = column;
+ this.columns = columns;
}
get documentKeys() {
- const docs = DocListCast(this.props.Document[this.props.fieldKey]);
-
- // let docs = this.childDocs;
+ let docs = this.childDocs;
let 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
@@ -725,33 +719,31 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@action
- textWrapRow = (doc: Doc): void => {
- let index = this._textWrappedRows.findIndex(id => doc[Id] === id);
+ toggleTextWrapRow = (doc: Doc): void => {
+ let textWrapped = this.textWrappedRows;
+ let index = textWrapped.findIndex(id => doc[Id] === id);
+
if (index > -1) {
- this._textWrappedRows.splice(index, 1);
+ textWrapped.splice(index, 1);
} else {
- this._textWrappedRows.push(doc[Id]);
+ textWrapped.push(doc[Id]);
}
+ this.textWrappedRows = textWrapped;
}
@computed
get reactTable() {
-
- // let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = DocListCast(cdoc[this.props.fieldKey]);
let children = this.childDocs;
-
- let previewWidth = this.previewWidth(); // + 2 * this.borderWidth + this.DIVIDER_WIDTH + 1;
let hasCollectionChild = children.reduce((found, doc) => found || doc.type === "collection", false);
let expandedRowsList = this._openCollections.map(col => children.findIndex(doc => doc[Id] === col).toString());
let expanded = {};
//@ts-ignore
expandedRowsList.forEach(row => expanded[row] = true);
- console.log(...[...this._textWrappedRows]); // TODO: get component to rerender on text wrap change without needign to console.log :((((
+ console.log("text wrapped rows", ...[...this.textWrappedRows]); // TODO: get component to rerender on text wrap change without needign to console.log :((((
return <ReactTable
- style={{ position: "relative", float: "left", width: `calc(100% - ${previewWidth}px` }}
+ style={{ position: "relative" }}
data={children}
page={0}
pageSize={children.length}
@@ -761,12 +753,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
getTdProps={this.getTdProps}
sortable={false}
TrComponent={MovableRow}
- sorted={Array.from(this._sortedColumns.values())}
+ sorted={this.sorted}
expanded={expanded}
+ resized={this.resized}
+ onResizedChange={this.onResizedChange}
SubComponent={hasCollectionChild ?
row => {
if (row.original.type === "collection") {
- // let childDocs = DocListCast(row.original[this.props.fieldKey]);
return <div className="sub"><SchemaTable {...this.props} Document={row.original} childDocs={undefined} /></div>;
}
}
@@ -775,6 +768,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
/>;
}
+ onResizedChange = (newResized: Resize[], event: any) => {
+ let columns = this.columns;
+ newResized.forEach(resized => {
+ let index = columns.findIndex(c => c.heading === resized.id);
+ let column = columns[index];
+ column.setWidth(resized.value);
+ columns[index] = column;
+ });
+ this.columns = columns;
+ }
+
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
@@ -786,8 +790,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
csv = csv.substr(0, csv.length - 1) + "\n";
let self = this;
- let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- // let children = DocListCast(cdoc[this.props.fieldKey]);
this.childDocs.map(doc => {
csv += self.columns.reduce((val, col) => val + (doc[col.heading] ? doc[col.heading]!.toString() : "0") + ",", "");
csv = csv.substr(0, csv.length - 1) + "\n";
@@ -805,11 +807,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
getField = (row: number, col?: number) => {
- // const docs = DocListCast(this.props.Document[this.props.fieldKey]);
-
- let cdoc = this.props.dataDoc ? this.props.dataDoc : this.props.Document;
- const docs = DocListCast(cdoc[this.props.fieldKey]);
- // let docs = this.childDocs;
+ let docs = this.childDocs;
row = row % docs.length;
while (row < 0) row += docs.length;
@@ -881,13 +879,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
render() {
- // if (SelectionManager.SelectedDocuments().length > 0) console.log(StrCast(SelectionManager.SelectedDocuments()[0].Document.title));
- // if (DocumentManager.Instance.getDocumentView(this.props.Document)) console.log(StrCast(this.props.Document.title), SelectionManager.IsSelected(DocumentManager.Instance.getDocumentView(this.props.Document)!))
return (
<div className="collectionSchemaView-table" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
onDrop={(e: React.DragEvent) => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
{this.reactTable}
- <button onClick={() => this.createRow()}>new row</button>
+ <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
</div>
);
}
@@ -984,6 +980,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
style={{
transform: `translate(${this.centeringOffset}px, 0px)`,
borderRadius: this.borderRounding,
+ display: "inline",
height: "100%"
}}>
<DocumentView
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 0cb01dc9d..015955816 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -79,10 +79,10 @@
.collectionStackingView-sectionHeader {
text-align: center;
- margin-left: 5px;
- margin-right: 5px;
+ margin-left: 2px;
+ margin-right: 2px;
margin-top: 10px;
- overflow: hidden;
+ // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong
.editableView-input {
color: black;
@@ -125,6 +125,43 @@
}
}
+ .collectionStackingView-sectionColor {
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+
+ [class*="css"] {
+ max-width: 102px;
+ }
+
+ .collectionStackingView-sectionColorButton {
+ height: 35px;
+ }
+
+ .collectionStackingView-colorPicker {
+ width: 78px;
+
+ .colorOptions {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .colorPicker {
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+ border-radius: 10px;
+ margin: 3px;
+
+ &.active {
+ border: 2px solid white;
+ box-shadow: 0 0 0 2px lightgray;
+ }
+ }
+ }
+ }
+
.collectionStackingView-sectionDelete {
position: absolute;
right: 0;
@@ -184,4 +221,53 @@
letter-spacing: 2px;
height: fit-content;
}
+
+ .rc-switch {
+ position: absolute;
+ display: inline-block;
+ bottom: 4px;
+ right: 4px;
+ width: 70px;
+ height: 30px;
+ border-radius: 40px 40px;
+ background-color: lightslategrey;
+ }
+
+ .rc-switch:after {
+ position: absolute;
+ width: 22px;
+ height: 22px;
+ left: 3px;
+ top: 4px;
+ border-radius: 50% 50%;
+ background-color: #fff;
+ content: " ";
+ cursor: pointer;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.26);
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ transition: left 0.3s cubic-bezier(0.35, 0, 0.25, 1);
+ -webkit-animation-timing-function: cubic-bezier(0.35, 0, 0.25, 1);
+ animation-timing-function: cubic-bezier(0.35, 0, 0.25, 1);
+ -webkit-animation-duration: 0.3s;
+ animation-duration: 0.3s;
+ }
+
+ .rc-switch-checked:after {
+ left: 44px;
+ }
+
+ .rc-switch-inner {
+ color: #fff;
+ font-size: 12px;
+ position: absolute;
+ left: 28px;
+ top: 8px;
+ }
+
+ .rc-switch-checked .rc-switch-inner {
+ left: 8px;
+ }
+
+
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index bcf3a85d7..9741b9e89 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -20,6 +20,7 @@ import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeade
import { List } from "../../../new_fields/List";
import { EditableView } from "../EditableView";
import { CollectionViewProps } from "./CollectionBaseView";
+import Switch from 'rc-switch';
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -248,7 +249,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
docList={docList}
parent={this}
type={type}
- createDropTarget={this.createDropTarget} />;
+ createDropTarget={this.createDropTarget}
+ screenToLocalTransform={this.props.ScreenToLocalTransform}
+ />;
+
}
@action
@@ -269,6 +273,14 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
return firstEntry[0].heading > secondEntry[0].heading ? 1 : -1;
}
+ onToggle = (checked: Boolean) => {
+ if (checked) {
+ this.props.CollectionView.props.Document.chromeStatus = 'collapsed';
+ } else {
+ this.props.CollectionView.props.Document.chromeStatus = 'view-mode';
+ }
+ }
+
render() {
let headings = Array.from(this.Sections.keys());
let editableViewProps = {
@@ -289,11 +301,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
{this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
map(section => this.section(section[0], section[1])) :
this.section(undefined, this.filteredChildren)}
- {(this.props.Document.sectionFilter && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
+ {(this.props.Document.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
<div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
- style={{ width: (this.columnWidth / (headings.length + (this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0))) - 10, marginTop: 10 }}>
+ style={{ width: (this.columnWidth / (headings.length + ((this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0))) - 10, marginTop: 10 }}>
<EditableView {...editableViewProps} />
</div> : null}
+ {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? <Switch
+ onChange={this.onToggle}
+ onClick={this.onToggle}
+ defaultChecked={this.props.CollectionView.props.Document.chromeStatus !== 'view-mode'}
+ checkedChildren="edit"
+ unCheckedChildren="view"
+ /> : null}
</div>
);
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index d8bed7e88..df03da376 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -14,11 +14,17 @@ import { DocumentManager } from "../../util/DocumentManager";
import { SelectionManager } from "../../util/SelectionManager";
import "./CollectionStackingView.scss";
import { Docs } from "../../documents/Documents";
-import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { SchemaHeaderField, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ScriptField } from "../../../new_fields/ScriptField";
import { CompileScript } from "../../util/Scripting";
import { RichTextField } from "../../../new_fields/RichTextField";
+import { Transform } from "../../util/Transform";
+import { Flyout, anchorPoints } from "../DocumentDecorations";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faPalette } from '@fortawesome/free-solid-svg-icons';
+
+library.add(faPalette);
interface CSVFieldColumnProps {
@@ -30,6 +36,7 @@ interface CSVFieldColumnProps {
parent: CollectionStackingView;
type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined;
createDropTarget: (ele: HTMLDivElement) => void;
+ screenToLocalTransform: () => Transform;
}
@observer
@@ -39,8 +46,11 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
private _dropRef: HTMLDivElement | null = null;
private dropDisposer?: DragManager.DragDropDisposer;
private _headerRef: React.RefObject<HTMLDivElement> = React.createRef();
+ private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
+ private _sensitivity: number = 16;
@observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading;
+ @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
createColumnDropRef = (ele: HTMLDivElement | null) => {
this._dropRef = ele;
@@ -150,6 +160,14 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
}
@action
+ changeColumnColor = (color: string) => {
+ if (this.props.headingObject) {
+ this.props.headingObject.setColor(color);
+ this._color = color;
+ }
+ }
+
+ @action
pointerEntered = () => {
if (SelectionManager.GetIsDragging()) {
this._background = "#b4b4b4";
@@ -159,6 +177,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
@action
pointerLeave = () => {
this._background = "inherit";
+ document.removeEventListener("pointermove", this.startDrag);
}
@action
@@ -180,22 +199,25 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
}
startDrag = (e: PointerEvent) => {
- let alias = Doc.MakeAlias(this.props.parent.props.Document);
- let key = StrCast(this.props.parent.props.Document.sectionFilter);
- let value = this.getValue(this._heading);
- value = typeof value === "string" ? `"${value}"` : value;
- let script = `return doc.${key} === ${value}`;
- let compiled = CompileScript(script, { params: { doc: Doc.name } });
- if (compiled.compiled) {
- let scriptField = new ScriptField(compiled);
- alias.viewSpecScript = scriptField;
- let dragData = new DragManager.DocumentDragData([alias], [alias.proto]);
- DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
- }
+ let [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
+ if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
+ let alias = Doc.MakeAlias(this.props.parent.props.Document);
+ let key = StrCast(this.props.parent.props.Document.sectionFilter);
+ let value = this.getValue(this._heading);
+ value = typeof value === "string" ? `"${value}"` : value;
+ let script = `return doc.${key} === ${value}`;
+ let compiled = CompileScript(script, { params: { doc: Doc.name } });
+ if (compiled.compiled) {
+ let scriptField = new ScriptField(compiled);
+ alias.viewSpecScript = scriptField;
+ let dragData = new DragManager.DocumentDragData([alias], [alias.proto]);
+ DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY);
+ }
- e.stopPropagation();
- document.removeEventListener("pointermove", this.startDrag);
- document.removeEventListener("pointerup", this.pointerUp);
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.startDrag);
+ document.removeEventListener("pointerup", this.pointerUp);
+ }
}
pointerUp = (e: PointerEvent) => {
@@ -210,12 +232,45 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
e.stopPropagation();
e.preventDefault();
+ let [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY);
+ this._startDragPosition = { x: dx, y: dy };
+
document.removeEventListener("pointermove", this.startDrag);
document.addEventListener("pointermove", this.startDrag);
document.removeEventListener("pointerup", this.pointerUp);
document.addEventListener("pointerup", this.pointerUp);
}
+ renderColorPicker = () => {
+ let selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb";
+
+ let pink = PastelSchemaPalette.get("pink2");
+ let purple = PastelSchemaPalette.get("purple4");
+ let blue = PastelSchemaPalette.get("bluegreen1");
+ let yellow = PastelSchemaPalette.get("yellow4");
+ let red = PastelSchemaPalette.get("red2");
+ let green = PastelSchemaPalette.get("bluegreen7");
+ let cyan = PastelSchemaPalette.get("bluegreen5");
+ let orange = PastelSchemaPalette.get("orange1");
+ let gray = "#f1efeb";
+
+ return (
+ <div className="collectionStackingView-colorPicker">
+ <div className="colorOptions">
+ <div className={"colorPicker" + (selected === pink ? " active" : "")} style={{ backgroundColor: pink }} onClick={() => this.changeColumnColor(pink!)}></div>
+ <div className={"colorPicker" + (selected === purple ? " active" : "")} style={{ backgroundColor: purple }} onClick={() => this.changeColumnColor(purple!)}></div>
+ <div className={"colorPicker" + (selected === blue ? " active" : "")} style={{ backgroundColor: blue }} onClick={() => this.changeColumnColor(blue!)}></div>
+ <div className={"colorPicker" + (selected === yellow ? " active" : "")} style={{ backgroundColor: yellow }} onClick={() => this.changeColumnColor(yellow!)}></div>
+ <div className={"colorPicker" + (selected === red ? " active" : "")} style={{ backgroundColor: red }} onClick={() => this.changeColumnColor(red!)}></div>
+ <div className={"colorPicker" + (selected === gray ? " active" : "")} style={{ backgroundColor: gray }} onClick={() => this.changeColumnColor(gray)}></div>
+ <div className={"colorPicker" + (selected === green ? " active" : "")} style={{ backgroundColor: green }} onClick={() => this.changeColumnColor(green!)}></div>
+ <div className={"colorPicker" + (selected === cyan ? " active" : "")} style={{ backgroundColor: cyan }} onClick={() => this.changeColumnColor(cyan!)}></div>
+ <div className={"colorPicker" + (selected === orange ? " active" : "")} style={{ backgroundColor: orange }} onClick={() => this.changeColumnColor(orange!)}></div>
+ </div>
+ </div>
+ );
+ }
+
render() {
let cols = this.props.cols();
let key = StrCast(this.props.parent.props.Document.sectionFilter);
@@ -242,7 +297,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
style={{
width: (style.columnWidth) /
((uniqueHeadings.length +
- (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) || 1)
+ ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)
}}>
{/* the default bucket (no key value) has a tooltip that describes what it is.
Further, it does not have a color and cannot be deleted. */}
@@ -251,11 +306,19 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
`Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""}
style={{
width: "100%",
- background: this.props.headingObject && evContents !== `NO ${key.toUpperCase()} VALUE` ?
- this.props.headingObject.color : "lightgrey",
+ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey",
color: "grey"
}}>
<EditableView {...headerEditableViewProps} />
+ {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) :
+ <div className="collectionStackingView-sectionColor">
+ <Flyout anchorPoint={anchorPoints.TOP_CENTER} content={this.renderColorPicker()}>
+ <button className="collectionStackingView-sectionColorButton">
+ <FontAwesomeIcon icon="palette" size="sm" />
+ </button>
+ </ Flyout >
+ </div>
+ }
{evContents === `NO ${key.toUpperCase()} VALUE` ?
(null) :
<button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}>
@@ -265,7 +328,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div> : (null);
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `;
return (
- <div key={heading} style={{ width: `${100 / ((uniqueHeadings.length + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) || 1)}%`, background: this._background }}
+ <div key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, background: this._background }}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
{headingView}
<div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
@@ -283,9 +346,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
{this.children(this.props.docList)}
{singleColumn ? (null) : this.props.parent.columnDragger}
</div>
- {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
+ {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
<div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
- style={{ width: style.columnWidth / (uniqueHeadings.length + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) }}>
+ style={{ width: style.columnWidth / (uniqueHeadings.length + 1) }}>
<EditableView {...newEditableViewProps} />
</div> : null}
</div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a15ed8f94..077f3f941 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,13 +1,15 @@
import { action, computed } from "mobx";
import * as rp from 'request-promise';
import CursorField from "../../../new_fields/CursorField";
-import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
+import { ScriptField } from "../../../new_fields/ScriptField";
import { BoolCast, Cast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { RouteStore } from "../../../server/RouteStore";
+import { Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs, DocumentOptions, DocumentType } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
@@ -19,10 +21,6 @@ import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
import { CollectionView } from "./CollectionView";
import React = require("react");
-import { MainView } from "../MainView";
-import { Utils } from "../../../Utils";
-import { ScriptField } from "../../../new_fields/ScriptField";
-import { CompileScript } from "../../util/Scripting";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -66,6 +64,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
if (res.success) {
return res.result;
}
+ else {
+ console.log(res.error);
+ }
});
}
return docs;
@@ -112,6 +113,13 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.mods === "AltKey" && de.data.draggedDocuments.length) {
+ this.childDocs.map(doc =>
+ Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, undefined)
+ );
+ e.stopPropagation();
+ return true;
+ }
let added = false;
if (de.data.dropAction || de.data.userDropAction) {
added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index db3652ff6..990979109 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -7,6 +7,9 @@
border-radius: inherit;
box-sizing: border-box;
height: 100%;
+ width:100%;
+ position: absolute;
+ top:0;
padding-top: 20px;
padding-left: 10px;
padding-right: 0px;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index b1e6eada0..7f5d78313 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -25,7 +25,6 @@ import { CollectionSchemaPreview } from './CollectionSchemaView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
-import { LinkManager } from '../../util/LinkManager';
import { ComputedField } from '../../../new_fields/ScriptField';
import { KeyValueBox } from '../nodes/KeyValueBox';
@@ -67,36 +66,24 @@ library.add(faPlus, faMinus);
* Component that takes in a document prop and a boolean whether it's collapsed or not.
*/
class TreeView extends React.Component<TreeViewProps> {
+ static loadId = "";
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
- @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "data"); }
- @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@observable _collapsed: boolean = true;
-
+ @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "fields"); }
+ @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
+ @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
@computed get fieldKey() {
- let target = this.props.document;
- let keys = Array.from(Object.keys(target)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
- if (target.proto instanceof Doc) {
- let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
- keys.push(...arr);
- while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
- }
- let keyList: string[] = [];
- keys.map(key => {
- let docList = Cast(this.dataDoc[key], listSpec(Doc));
- if (docList && docList.length > 0) {
- keyList.push(key);
- }
- });
- let layout = StrCast(this.props.document.layout);
- if (layout.indexOf("fieldKey={\"") !== -1 && layout.indexOf("fieldExt=") === -1) {
- return layout.split("fieldKey={\"")[1].split("\"")[0];
- }
- return keyList.length ? keyList[0] : "data";
+ let splits = StrCast(this.props.document.layout).split("fieldKey={\"");
+ return splits.length > 1 ? splits[1].split("\"")[0] : "data";
+ }
+ @computed get childDocs() {
+ let layout = this.props.document.layout as Doc;
+ return (this.props.dataDoc ? Cast(this.props.dataDoc[this.fieldKey], listSpec(Doc)) : undefined) ||
+ (layout ? Cast(layout[this.fieldKey], listSpec(Doc)) : undefined) ||
+ Cast(this.props.document[this.fieldKey], listSpec(Doc));
}
-
- @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
@computed get resolvedDataDoc() {
if (this.props.dataDoc === undefined && this.props.document.layout instanceof Doc) {
// if there is no dataDoc (ie, we're not rendering a template layout), but this document
@@ -104,18 +91,32 @@ class TreeView extends React.Component<TreeViewProps> {
// this document as the data document for the layout.
return this.props.document;
}
- return this.props.dataDoc ? this.props.dataDoc : undefined;
+ return this.props.dataDoc;
+ }
+ @computed get boundsOfCollectionDocument() {
+ return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 ? undefined :
+ Doc.ComputeContentBounds(DocListCast(this.props.document.data));
}
- protected createTreeDropTarget = (ele: HTMLDivElement) => {
- this._treedropDisposer && this._treedropDisposer();
- if (ele) {
- this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } });
+ @undoBatch delete = () => this.props.deleteDoc(this.dataDoc);
+ @undoBatch openRight = () => this.props.addDocTab(this.props.document, undefined, "onRight");
+ @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete();
+ @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => {
+ return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
+ }
+ @undoBatch @action remove = (document: Document, key: string): boolean => {
+ let children = Cast(this.dataDoc[key], listSpec(Doc), []);
+ if (children.indexOf(document) !== -1) {
+ children.splice(children.indexOf(document), 1);
+ return true;
}
+ return false;
}
- @undoBatch delete = () => this.props.deleteDoc(this.dataDoc);
- @undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight");
+ protected createTreeDropTarget = (ele: HTMLDivElement) => {
+ this._treedropDisposer && this._treedropDisposer();
+ ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }));
+ }
onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
onPointerEnter = (e: React.PointerEvent): void => {
@@ -144,34 +145,6 @@ class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}
- @action
- remove = (document: Document, key: string): boolean => {
- let children = Cast(this.dataDoc[key], listSpec(Doc), []);
- if (children.indexOf(document) !== -1) {
- children.splice(children.indexOf(document), 1);
- return true;
- }
- return false;
- }
-
- @action
- move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
- return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
- }
- @action
- indent = () => this.props.addDocument(this.props.document) && this.delete()
-
- renderBullet() {
- let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc));
- let doc = Cast(this.dataDoc[this.fieldKey], Doc);
- let isDoc = doc instanceof Doc || docList;
- let c;
- return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
- {<FontAwesomeIcon icon={this._collapsed ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />}
- </div>;
- }
-
- static loadId = "";
editableView = (key: string, style?: string) => (<EditableView
oneLine={true}
display={"inline"}
@@ -192,43 +165,6 @@ class TreeView extends React.Component<TreeViewProps> {
OnTab={() => this.props.indentDocument && this.props.indentDocument()}
/>)
- /**
- * Renders the EditableView title element for placement into the tree.
- */
- renderTitle() {
- let reference = React.createRef<HTMLDivElement>();
- let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
-
- let headerElements = (
- <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
- onPointerDown={action(() => {
- this.props.document.treeViewExpandedView = this.treeViewExpandedView === "data" ? "fields" :
- this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : "data";
- this._collapsed = false;
- })}>
- {this.treeViewExpandedView}
- </span>);
- let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
- let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : (
- <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
- <FontAwesomeIcon icon="angle-right" size="lg" />
- </div>);
- return <>
- <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
- style={{
- background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0",
- outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
- pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
- }}
- >
- {this.editableView("title")}
- {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
- </div >
- {headerElements}
- {openRight}
- </>;
- }
-
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
@@ -289,39 +225,6 @@ class TreeView extends React.Component<TreeViewProps> {
let finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
return finalXf;
}
-
- renderLinks = () => {
- let ele: JSX.Element[] = [];
- let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this.fieldKey, doc, addBefore, before);
- let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
- groups.forEach((groupLinkDocs, groupType) => {
- // let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
- let destLinks: Doc[] = [];
- groupLinkDocs.forEach((doc) => {
- let opp = LinkManager.Instance.getOppositeAnchor(doc, this.props.document);
- if (opp) {
- destLinks.push(opp);
- }
- });
- ele.push(
- <div key={"treeviewlink-" + groupType + "subtitle"}>
- <div className="collectionTreeView-subtitle">{groupType}:</div>
- {
- TreeView.GetChildElements(destLinks, this.props.treeViewId, this.props.document, this.props.dataDoc, "treeviewlink-" + groupType, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)
- }
- </div>
- );
- });
- return ele;
- }
-
- @computed get boundsOfCollectionDocument() {
- if (StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1) return undefined;
- let layoutDoc = this.props.document;
- return Doc.ComputeContentBounds(DocListCast(layoutDoc.data));
- }
docWidth = () => {
let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 5));
@@ -337,39 +240,29 @@ class TreeView extends React.Component<TreeViewProps> {
})());
}
- noOverlays = (doc: Doc) => ({ title: "", caption: "" });
-
- expandedField = (doc?: Doc) => {
- if (!doc) return <div />;
- let realDoc = doc;
-
+ expandedField = (doc: Doc) => {
let ids: { [key: string]: string } = {};
- Object.keys(doc).forEach(key => {
- if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
- ids[key] = key;
- }
- });
+ doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
let rows: JSX.Element[] = [];
for (let key of Object.keys(ids).sort()) {
- let contents = realDoc[key] ? realDoc[key] : undefined;
+ let contents = doc[key];
let contentElement: JSX.Element[] | JSX.Element = [];
if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
- let docList = contents;
let remDoc = (doc: Doc) => this.remove(doc, key);
let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before);
- contentElement = key === "links" ? this.renderLinks() :
- TreeView.GetChildElements(docList instanceof Doc ? [docList] : DocListCast(docList), this.props.treeViewId, realDoc, undefined, key, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+ contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
+ DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
} else {
contentElement = <EditableView
key="editableView"
- contents={contents ? contents.toString() : "null"}
+ contents={contents !== undefined ? contents.toString() : "null"}
height={13}
fontSize={12}
- GetValue={() => Field.toKeyValueString(realDoc, key)}
- SetValue={(value: string) => KeyValueBox.SetField(realDoc, key, value)} />;
+ GetValue={() => Field.toKeyValueString(doc, key)}
+ SetValue={(value: string) => KeyValueBox.SetField(doc, key, value)} />;
}
rows.push(<div style={{ display: "flex" }} key={key}>
<span style={{ fontWeight: "bold" }}>{key + ":"}</span>
@@ -380,56 +273,100 @@ class TreeView extends React.Component<TreeViewProps> {
return rows;
}
- render() {
- let contentElement: (JSX.Element | null) = null;
- let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc));
- let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before);
+ noOverlays = (doc: Doc) => ({ title: "", caption: "" });
- if (!this._collapsed) {
- if (this.treeViewExpandedView === "data") {
- let doc = Cast(this.props.document[this.fieldKey], Doc);
- contentElement = <ul key={this.fieldKey + "more"}>
- {this.fieldKey === "links" ? this.renderLinks() :
- TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
- </ul >;
- } else if (this.treeViewExpandedView === "fields") {
- contentElement = <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
- {this.expandedField(this.dataDoc)}
- </div></ul>;
- } else {
- let layoutDoc = this.props.document;
- contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
- <CollectionSchemaPreview
- Document={layoutDoc}
- DataDocument={this.resolvedDataDoc}
- renderDepth={this.props.renderDepth}
- showOverlays={this.noOverlays}
- fitToBox={this.boundsOfCollectionDocument !== undefined}
- width={this.docWidth}
- height={this.docHeight}
- getTransform={this.docTransform}
- CollectionView={undefined}
- addDocument={emptyFunction as any}
- moveDocument={this.props.moveDocument}
- removeDocument={emptyFunction as any}
- active={this.props.active}
- whenActiveChanged={emptyFunction as any}
- addDocTab={this.props.addDocTab}
- setPreviewScript={emptyFunction}>
- </CollectionSchemaPreview>
- </div>;
- }
+ @computed get renderContent() {
+ if (this.treeViewExpandedView === this.fieldKey) {
+ let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before);
+ return <ul key={this.fieldKey + "more"}>
+ {!this.childDocs ? (null) :
+ TreeView.GetChildElements(this.childDocs as Doc[], this.props.treeViewId, this.props.document.layout as Doc,
+ this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform,
+ this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
+ </ul >;
+ } else if (this.treeViewExpandedView === "fields") {
+ return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
+ {this.dataDoc ? this.expandedField(this.dataDoc) : (null)}
+ </div></ul>;
+ } else {
+ let layoutDoc = this.props.document;
+ return <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}>
+ <CollectionSchemaPreview
+ Document={layoutDoc}
+ DataDocument={this.resolvedDataDoc}
+ renderDepth={this.props.renderDepth}
+ showOverlays={this.noOverlays}
+ fitToBox={this.boundsOfCollectionDocument !== undefined}
+ width={this.docWidth}
+ height={this.docHeight}
+ getTransform={this.docTransform}
+ CollectionView={undefined}
+ addDocument={emptyFunction as any}
+ moveDocument={this.props.moveDocument}
+ removeDocument={emptyFunction as any}
+ active={this.props.active}
+ whenActiveChanged={emptyFunction as any}
+ addDocTab={this.props.addDocTab}
+ setPreviewScript={emptyFunction}>
+ </CollectionSchemaPreview>
+ </div>;
}
+ }
+
+ @computed
+ get renderBullet() {
+ return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={this._collapsed ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+ </div>;
+ }
+ /**
+ * Renders the EditableView title element for placement into the tree.
+ */
+ @computed
+ get renderTitle() {
+ let reference = React.createRef<HTMLDivElement>();
+ let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
+
+ let headerElements = (
+ <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
+ onPointerDown={action(() => {
+ this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
+ this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
+ this.childDocs ? this.fieldKey : "fields";
+ this._collapsed = false;
+ })}>
+ {this.treeViewExpandedView}
+ </span>);
+ let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
+ let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : (
+ <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
+ <FontAwesomeIcon icon="angle-right" size="lg" />
+ </div>);
+ return <>
+ <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
+ style={{
+ background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0",
+ outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
+ pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
+ }} >
+ {this.editableView("title")}
+ </div >
+ {headerElements}
+ {openRight}
+ </>;
+ }
+
+ render() {
return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}>
<li className="collection-child">
<div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
- {this.renderBullet()}
- {this.renderTitle()}
+ {this.renderBullet}
+ {this.renderTitle}
</div>
<div className="treeViewItem-border">
- {contentElement}
+ {this._collapsed ? (null) : this.renderContent}
</div>
</li>
</div>;
@@ -454,6 +391,8 @@ class TreeView extends React.Component<TreeViewProps> {
let docList = docs.filter(child => !child.excludeFromLibrary);
let rowWidth = () => panelWidth() - 20;
return docList.map((child, i) => {
+ let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child);
+
let indent = i === 0 ? undefined : () => {
if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) {
let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1];
@@ -470,8 +409,8 @@ class TreeView extends React.Component<TreeViewProps> {
return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym]();
};
return <TreeView
- document={child}
- dataDoc={dataDoc}
+ document={pair.layout}
+ dataDoc={pair.data}
containingCollection={containingCollection}
treeViewId={treeViewId}
key={child[Id]}
@@ -497,7 +436,9 @@ export class CollectionTreeView extends CollectionSubView(Document) {
private treedropDisposer?: DragManager.DragDropDisposer;
private _mainEle?: HTMLDivElement;
- @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
+ @observable static NotifsCol: Opt<Doc>;
+
+ @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
protected createTreeDropTarget = (ele: HTMLDivElement) => {
this.treedropDisposer && this.treedropDisposer();
@@ -529,21 +470,15 @@ export class CollectionTreeView extends CollectionSubView(Document) {
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
}
-
- @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
-
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
-
-
- @observable static NotifsCol: Opt<Doc>;
-
openNotifsCol = () => {
if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) {
CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined);
}
}
- @computed get notifsButton() {
+
+ @computed get renderNotifsButton() {
const length = CollectionTreeView.NotifsCol ? DocListCast(CollectionTreeView.NotifsCol.data).length : 0;
const notifsRef = React.createRef<HTMLDivElement>();
const dragNotifs = action(() => CollectionTreeView.NotifsCol!);
@@ -559,18 +494,15 @@ export class CollectionTreeView extends CollectionSubView(Document) {
</div>
</div >;
}
- @computed get clearButton() {
+ @computed get renderClearButton() {
return <div id="toolbar" key="toolbar">
- <div >
- <button className="toolbar-button round-button" title="Notifs"
- onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
- <FontAwesomeIcon icon={faTrash} size="sm" />
- </button>
- </div>
+ <button className="toolbar-button round-button" title="Notifs"
+ onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
+ <FontAwesomeIcon icon={faTrash} size="sm" />
+ </button>
</div >;
}
-
render() {
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
@@ -596,8 +528,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
TreeView.loadId = doc[Id];
Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
}} />
- {this.props.Document.workspaceLibrary ? this.notifsButton : (null)}
- {this.props.Document.allowClear ? this.clearButton : (null)}
+ {this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)}
+ {this.props.Document.allowClear ? this.renderClearButton : (null)}
<ul className="no-indent" style={{ width: "max-content" }} >
{
TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 212cc5477..57dc5879b 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,11 +1,13 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faProjectDiagram, faSignature, faColumns, faSquare, faTh, faImage, faThList, faTree, faEllipsisV, faFingerprint, faLaptopCode } from '@fortawesome/free-solid-svg-icons';
+import { faEye } from '@fortawesome/free-regular-svg-icons';
+import { faColumns, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons';
+import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
import { observer } from "mobx-react";
import * as React from 'react';
-import { Doc, DocListCast, WidthSym, HeightSym } from '../../../new_fields/Doc';
+import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
+import { StrCast } from '../../../new_fields/Types';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
import { FieldView, FieldViewProps } from '../nodes/FieldView';
@@ -15,11 +17,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
import { CollectionSchemaView } from "./CollectionSchemaView";
import { CollectionStackingView } from './CollectionStackingView';
import { CollectionTreeView } from "./CollectionTreeView";
-import { StrCast, PromiseValue } from '../../../new_fields/Types';
-import { DocumentType } from '../../documents/Documents';
-import { CollectionStackingViewChrome, CollectionViewBaseChrome } from './CollectionViewChromes';
-import { observable, action, runInAction, IReactionDisposer, reaction } from 'mobx';
-import { faEye } from '@fortawesome/free-regular-svg-icons';
+import { CollectionViewBaseChrome } from './CollectionViewChromes';
export const COLLECTION_BORDER_WIDTH = 2;
library.add(faTh);
@@ -45,6 +43,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus),
() => {
// chrome status is one of disabled, collapsed, or visible. this determines initial state from document
+ // chrome status may also be view-mode, in reference to stacking view's toggle mode. it is essentially disabled mode, but prevents the toggle button from showing up on the left sidebar.
let chromeStatus = this.props.Document.chromeStatus;
if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) {
runInAction(() => this._collapsed = true);
@@ -112,6 +111,9 @@ export class CollectionView extends React.Component<FieldViewProps> {
}
ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" });
ContextMenu.Instance.addItem({ description: "Apply Template", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" });
+ ContextMenu.Instance.addItem({
+ description: this.props.Document.chromeStatus !== "disabled" ? "Hide Chrome" : "Show Chrome", event: () => this.props.Document.chromeStatus = (this.props.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram"
+ });
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index 989315194..793cb7a8b 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -7,8 +7,6 @@
z-index: 9001;
transition: top .5s;
background: lightgrey;
- transition: margin-top .5s;
- background: lightgray;
padding: 10px;
.collectionViewChrome {
@@ -16,6 +14,7 @@
grid-template-columns: 1fr auto;
padding-bottom: 10px;
border-bottom: .5px solid rgb(180, 180, 180);
+ overflow: hidden;
.collectionViewBaseChrome {
display: flex;
@@ -37,9 +36,11 @@
}
.collectionViewBaseChrome-collapse {
- transition: all .5s;
+ transition: all .5s, opacity 0.3s;
position: absolute;
width: 40px;
+ transform-origin: top left;
+ // margin-top: 10px;
}
.collectionViewBaseChrome-viewSpecs {
@@ -168,4 +169,55 @@
cursor: text;
}
}
+}
+
+.collectionSchemaViewChrome-cont {
+ display: flex;
+ font-size: 10.5px;
+
+ .collectionSchemaViewChrome-toggle {
+ display: flex;
+ margin-left: 10px;
+ }
+
+ .collectionSchemaViewChrome-label {
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ margin-right: 5px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
+
+ .collectionSchemaViewChrome-toggler {
+ width: 100px;
+ height: 41px;
+ background-color: black;
+ position: relative;
+ }
+
+ .collectionSchemaViewChrome-togglerButton {
+ width: 47px;
+ height: 35px;
+ background-color: $light-color-secondary;
+ // position: absolute;
+ transition: all 0.5s ease;
+ // top: 3px;
+ margin-top: 3px;
+ color: gray;
+ letter-spacing: 2px;
+ text-transform: uppercase;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+
+ &.on {
+ margin-left: 3px;
+ }
+
+ &.off {
+ margin-left: 50px;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 38aafd3cc..1b2561953 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -17,6 +17,10 @@ import { CompileScript } from "../../util/Scripting";
import { ScriptField } from "../../../new_fields/ScriptField";
import { CollectionSchemaView } from "./CollectionSchemaView";
import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
+import { listSpec } from "../../../new_fields/Schema";
+import { List } from "../../../new_fields/List";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { threadId } from "worker_threads";
const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
@@ -142,7 +146,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
`return ${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} ${keyRestrictionScript}` :
`return ${keyRestrictionScript} ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
"return true";
- let compiled = CompileScript(fullScript, { params: { doc: Doc.name } });
+ let compiled = CompileScript(fullScript, { params: { doc: Doc.name }, typecheck: false });
if (compiled.compiled) {
this.props.CollectionView.props.Document.viewSpecScript = new ScriptField(compiled);
}
@@ -185,11 +189,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
render() {
return (
- <div className="collectionViewChrome-cont" style={{ marginTop: this._collapsed ? -70 : 0, height: 70 }}>
+ <div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -70 : 0 }}>
<div className="collectionViewChrome">
<div className="collectionViewBaseChrome">
<button className="collectionViewBaseChrome-collapse"
- style={{ marginTop: this._collapsed ? 60 : 0, transform: `rotate(${this._collapsed ? 180 : 0}deg)` }}
+ style={{
+ top: this._collapsed ? 70 : 10,
+ transform: `rotate(${this._collapsed ? 180 : 0}deg) scale(${this._collapsed ? 0.5 : 1}) translate(${this._collapsed ? "-100%, -100%" : "0, 0"})`,
+ opacity: (this._collapsed && !this.props.CollectionView.props.isSelected()) ? 0 : 0.9,
+ left: (this._collapsed ? 0 : "unset"),
+ }}
title="Collapse collection chrome" onClick={this.toggleCollapse}>
<FontAwesomeIcon icon="caret-up" size="2x" />
</button>
@@ -204,7 +213,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="5">Stacking View</option>
<option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} value="6">Masonry View</option>
</select>
- <div className="collectionViewBaseChrome-viewSpecs">
+ <div className="collectionViewBaseChrome-viewSpecs" style={{ display: this._collapsed ? "none" : "grid" }}>
<input className="collectionViewBaseChrome-viewSpecsInput"
placeholder="FILTER DOCUMENTS"
value={this.filterValue ? this.filterValue.script.originalScript : ""}
@@ -366,7 +375,9 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@observer
export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
+ // private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+ @undoBatch
togglePreview = () => {
let dividerWidth = 4;
let borderWidth = Number(COLLECTION_BORDER_WIDTH);
@@ -374,16 +385,56 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
let previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
let tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
this.props.CollectionView.props.Document.schemaPreviewWidth = previewWidth === 0 ? Math.min(tableWidth / 3, 200) : 0;
+ }
+ @undoBatch
+ @action
+ toggleTextwrap = async () => {
+ let textwrappedRows = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []);
+ if (textwrappedRows.length) {
+ this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>([]);
+ } else {
+ let docs: Doc | Doc[] | Promise<Doc> | Promise<Doc[]> | (() => DocLike)
+ = () => DocListCast(this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldExt ? this.props.CollectionView.props.fieldExt : this.props.CollectionView.props.fieldKey]);
+ if (typeof docs === "function") {
+ docs = docs();
+ }
+ docs = await docs;
+ if (docs instanceof Doc) {
+ let allRows = [docs[Id]];
+ this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ } else {
+ let allRows = docs.map(doc => doc[Id]);
+ this.props.CollectionView.props.Document.textwrappedSchemaRows = new List<string>(allRows);
+ }
+ }
}
render() {
let previewWidth = NumCast(this.props.CollectionView.props.Document.schemaPreviewWidth);
+ let textWrapped = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
+
return (
- <div className="collectionStackingViewChrome-cont">
- <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={previewWidth !== 0} onChange={this.togglePreview} />Show Preview</div>
- </div>
+ <div className="collectionSchemaViewChrome-cont">
+ <div className="collectionSchemaViewChrome-toggle">
+ <div className="collectionSchemaViewChrome-label">Wrap Text: </div>
+ <div className="collectionSchemaViewChrome-toggler" onClick={this.toggleTextwrap}>
+ <div className={"collectionSchemaViewChrome-togglerButton" + (textWrapped ? " on" : " off")}>
+ {textWrapped ? "on" : "off"}
+ </div>
+ </div>
+ </div>
+
+ <div className="collectionSchemaViewChrome-toggle">
+ <div className="collectionSchemaViewChrome-label">Show Preview: </div>
+ <div className="collectionSchemaViewChrome-toggler" onClick={this.togglePreview}>
+ <div className={"collectionSchemaViewChrome-togglerButton" + (previewWidth !== 0 ? " on" : " off")}>
+ {previewWidth !== 0 ? "on" : "off"}
+ </div>
+ </div>
+ </div>
+ </div >
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx
index 9baa250a6..1b59547d8 100644
--- a/src/client/views/collections/KeyRestrictionRow.tsx
+++ b/src/client/views/collections/KeyRestrictionRow.tsx
@@ -2,6 +2,7 @@ import * as React from "react";
import { observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
+import { Doc } from "../../../new_fields/Doc";
interface IKeyRestrictionProps {
contains: boolean;
@@ -23,7 +24,10 @@ export default class KeyRestrictionRow extends React.Component<IKeyRestrictionPr
parsedValue = parsed;
type = "number";
}
- let scriptText = `${this._contains ? "" : "!"}((doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))`;
+ let scriptText = `${this._contains ? "" : "!"}(((doc.${this._key} && (doc.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))) ||
+ ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === "string" ? ".includes" : "<="}(${parsedValue}))))`;
+ // let doc = new Doc();
+ // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello"));
this.props.script(scriptText);
}
else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index cca199afa..c4311fa52 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -46,6 +46,7 @@
border-radius: inherit;
box-sizing: border-box;
position: absolute;
+ overflow: hidden;
.marqueeView {
overflow: hidden;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c4c3ba084..8cac60edb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,25 +1,35 @@
-import { action, computed, trace } from "mobx";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne } from "../../../../Utils";
+import { emptyFunction, returnOne, Utils } from "../../../../Utils";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
+import { CompileScript } from "../../../util/Scripting";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch, UndoManager } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
-import { SubmenuProps, ContextMenuProps } from "../../ContextMenuItem";
+import { ContextMenu } from "../../ContextMenu";
+import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingCanvas } from "../../InkingCanvas";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
import { pageSchema } from "../../nodes/ImageBox";
+import { OverlayElementOptions, OverlayView } from "../../OverlayView";
import PDFMenu from "../../pdf/PDFMenu";
+import { ScriptBox } from "../../ScriptBox";
import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -27,20 +37,8 @@ import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
-import { ScriptField } from "../../../../new_fields/ScriptField";
-import { OverlayView, OverlayElementOptions } from "../../OverlayView";
-import { ScriptBox } from "../../ScriptBox";
-import { CompileScript } from "../../../util/Scripting";
-import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass } from "@fortawesome/free-solid-svg-icons";
-import { undo } from "prosemirror-history";
-import { number } from "prop-types";
-import { ContextMenu } from "../../ContextMenu";
-import { SwatchesPicker } from "react-color";
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass);
+library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
export const panZoomSchema = createSchema({
panX: "number",
@@ -79,12 +77,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@computed get contentBounds() {
- let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined;
- return {
+ let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
+ let res = {
panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
scale: (bounds ? Math.min(this.props.PanelHeight() / (bounds.b - bounds.y), this.props.PanelWidth() / (bounds.r - bounds.x)) : this.Document.scale || 1) / this.parentScaling
};
+ if (res.scale === 0) res.scale = 1;
+ return res;
}
@computed get fitToBox() { return this.props.fitToBox || this.props.Document.fitToBox; }
@@ -98,6 +98,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+ private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
@@ -148,12 +149,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
drop = (e: Event, de: DragManager.DropEvent) => {
let xf = this.getTransform();
+ let xfo = this.getTransformOverlay();
+ let [xp, yp] = xf.transformPoint(de.x, de.y);
+ let [xpo, ypo] = xfo.transformPoint(de.x, de.y);
if (super.drop(e, de)) {
if (de.data instanceof DragManager.DocumentDragData) {
if (de.data.droppedDocuments.length) {
- let [xp, yp] = xf.transformPoint(de.x, de.y);
- let x = xp - de.data.xOffset;
- let y = yp - de.data.yOffset;
+ let z = NumCast(de.data.draggedDocuments[0].z);
+ let x = (z ? xpo : xp) - de.data.xOffset;
+ let y = (z ? ypo : yp) - de.data.yOffset;
let dropX = NumCast(de.data.droppedDocuments[0].x);
let dropY = NumCast(de.data.droppedDocuments[0].y);
de.data.droppedDocuments.forEach(d => {
@@ -203,7 +207,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
else if (de.data instanceof DragManager.AnnotationDragData) {
if (de.data.dropDocument) {
let dragDoc = de.data.dropDocument;
- let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
let x = xp - de.data.xOffset;
let y = yp - de.data.yOffset;
let dropX = NumCast(de.data.dropDocument.x);
@@ -270,10 +273,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this._pheight / this.zoomScaling());
let panelwidth = panelDim[0];
let panelheight = panelDim[1];
- // if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
- // if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
- // if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
- // if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
+ if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
+ if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
+ if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
+ if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
}
this.setPan(x - dx, y - dy);
this._lastX = e.pageX;
@@ -339,8 +342,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY;
- // this.props.Document.panX = panX;
- // this.props.Document.panY = panY;
if (this.props.Document.scrollY) {
this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
}
@@ -355,7 +356,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onDragOver = (): void => {
}
- bringToFront = (doc: Doc) => {
+ bringToFront = (doc: Doc, sendToBack?: boolean) => {
+ if (sendToBack) {
+ doc.zIndex = 0;
+ return;
+ }
const docs = this.childDocs;
docs.slice().sort((doc1, doc2) => {
if (doc1 === doc) return 1;
@@ -439,7 +444,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
- ScreenToLocalTransform: this.getTransform,
+ ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
renderDepth: this.props.renderDepth + 1,
selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
PanelWidth: pair.layout[WidthSym],
@@ -479,23 +484,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
};
}
- getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } {
+ getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } {
const result = script.script.run(params);
if (!result.success) {
return {};
}
- return result.result === undefined ? {} : result.result;
+ let doc = params.doc;
+ return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result;
}
- private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined {
+ private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } } | undefined {
if (viewDef.type === "text") {
const text = Cast(viewDef.text, "string");
const x = Cast(viewDef.x, "number");
const y = Cast(viewDef.y, "number");
+ const z = Cast(viewDef.z, "number");
const width = Cast(viewDef.width, "number");
const height = Cast(viewDef.height, "number");
const fontSize = Cast(viewDef.fontSize, "number");
- if ([text, x, y, width, height].some(val => val === undefined)) {
+ if ([text, x, y, z, width, height].some(val => val === undefined)) {
return undefined;
}
@@ -503,7 +510,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
ele: <div className="collectionFreeform-customText" style={{
transform: `translate(${x}px, ${y}px)`,
width, height, fontSize
- }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! }
+ }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
};
}
}
@@ -515,7 +522,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const script = this.Document.arrangeScript;
let state: any = undefined;
const docs = this.childDocs;
- let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = [];
+ let elements: { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } }[] = [];
if (initScript) {
const initResult = initScript.script.run({ docs, collection: this.Document });
if (initResult.success) {
@@ -533,15 +540,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => {
var page = NumCast(doc.page, -1);
- let bounds: { x?: number, y?: number, width?: number, height?: number };
if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) {
let minim = BoolCast(doc.isMinimized);
if (minim === undefined || !minim) {
- const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
+ const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
+ { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
state = pos.state === undefined ? state : pos.state;
prev.push({
- ele: <CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />,
- bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined
+ ele: <CollectionFreeFormDocumentView key={doc[Id]}
+ x={script ? pos.x : undefined} y={script ? pos.y : undefined}
+ width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
+ bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined
});
}
}
@@ -555,15 +564,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@computed.struct
get views() {
- return this.elements.map(ele => ele.ele);
+ return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
+ }
+ @computed.struct
+ get overlayViews() {
+ return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
}
+
@action
onCursorMove = (e: React.PointerEvent) => {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- onContextMenu = () => {
+ onContextMenu = (e: React.MouseEvent) => {
let layoutItems: ContextMenuProps[] = [];
layoutItems.push({
description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`,
@@ -608,6 +622,35 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData);
}, icon: "paint-brush"
});
+ ContextMenu.Instance.addItem({
+ description: "Import document", icon: "upload", event: () => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".zip";
+ input.onchange = async _e => {
+ const files = input.files;
+ if (!files) return;
+ const file = files[0];
+ let formData = new FormData();
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const upload = Utils.prepend("/uploadDoc");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json === "error") {
+ return;
+ }
+ const doc = await DocServer.GetRefField(json);
+ if (!doc || !(doc instanceof Doc)) {
+ return;
+ }
+ const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY);
+ doc.x = x, doc.y = y;
+ this.addDocument(doc, false);
+ };
+ input.click();
+ }
+ });
}
@@ -615,6 +658,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
...this.views
]
+ private overlayChildViews = () => {
+ console.log(this.overlayViews.length);
+ return [
+ ...this.overlayViews
+ ];
+ }
public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
return () => {
@@ -663,6 +712,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
</CollectionFreeFormViewPannableContents>
</MarqueeView>
+ {this.overlayChildViews()}
<CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 67bed284f..b9ee588dd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -135,7 +135,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
doc.width = 200;
docList.push(doc);
}
- let newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+ let newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
this.props.addDocument(newCol, false);
}
@@ -370,15 +370,25 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let selRect = this.Bounds;
let selection: Doc[] = [];
this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
- var z = NumCast(doc.zoomBasis, 1);
var x = NumCast(doc.x);
var y = NumCast(doc.y);
- var w = NumCast(doc.width) / z;
- var h = NumCast(doc.height) / z;
+ var w = NumCast(doc.width);
+ var h = NumCast(doc.height);
if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
selection.push(doc);
}
});
+ if (!selection.length) {
+ this.props.activeDocuments().map(doc => {
+ var x = NumCast(doc.x);
+ var y = NumCast(doc.y);
+ var w = NumCast(doc.width);
+ var h = NumCast(doc.height);
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
+ selection.push(doc);
+ }
+ });
+ }
return selection;
}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ffd760e0..3b6c443c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,6 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
transformOrigin: "left top",
position: "absolute",
backgroundColor: "transparent",
+ boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
borderRadius: this.borderRounding(),
transform: this.transform,
transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2dc2c18a3..c5cf2e23a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,19 +1,24 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import * as rp from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Copy, Id } from '../../../new_fields/FieldSymbols';
import { List } from "../../../new_fields/List";
import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
+import { RouteStore } from '../../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentType } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ClientUtils } from '../../util/ClientUtils';
+import DictationManager from '../../util/DictationManager';
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, dropActionType } from "../../util/DragManager";
-import { SearchUtil } from "../../util/SearchUtil";
+import { LinkManager } from '../../util/LinkManager';
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -22,29 +27,22 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from "../DocComponent";
+import { EditableView } from '../EditableView';
+import { OverlayView } from '../OverlayView';
import { PresentationView } from "../presentationview/PresentationView";
-import { Template, Templates } from "./../Templates";
+import { ScriptingRepl } from '../ScriptingRepl';
+import { Template } from "./../Templates";
import { DocumentContentsView } from "./DocumentContentsView";
-import * as rp from "request-promise";
import "./DocumentView.scss";
-import React = require("react");
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { list, object, createSimpleSchema } from 'serializr';
-import { LinkManager } from '../../util/LinkManager';
-import { RouteStore } from '../../../server/RouteStore';
import { FormattedTextBox } from './FormattedTextBox';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
-import { ClientUtils } from '../../util/ClientUtils';
-import { EditableView } from '../EditableView';
-import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
-import { DocumentDecorations } from '../DocumentDecorations';
+import React = require("react");
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
library.add(fa.faTrash);
library.add(fa.faShare);
+library.add(fa.faDownload);
library.add(fa.faExpandArrowsAlt);
library.add(fa.faCompressArrowsAlt);
library.add(fa.faLayerGroup);
@@ -62,7 +60,7 @@ library.add(fa.faCrosshairs);
library.add(fa.faDesktop);
library.add(fa.faUnlock);
library.add(fa.faLock);
-library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake);
+library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCompass, fa.faSnowflake, fa.faMicrophone);
// const linkSchema = createSchema({
// title: "string",
@@ -93,7 +91,7 @@ export interface DocumentViewProps {
selectOnLoad: boolean;
parentActive: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
- bringToFront: (doc: Doc) => void;
+ bringToFront: (doc: Doc, sendToBack?: boolean) => void;
addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
zoomToScale: (scale: number) => void;
@@ -118,6 +116,7 @@ export const positionSchema = createSchema({
height: "number",
x: "number",
y: "number",
+ z: "number",
});
export type PositionDocument = makeInterface<[typeof positionSchema]>;
@@ -526,7 +525,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
makeBackground = (): void => {
- this.props.Document.isBackground = true;
+ this.props.Document.isBackground = !this.props.Document.isBackground;
+ this.props.Document.isBackground && this.props.bringToFront(this.props.Document, true);
}
@undoBatch
@@ -535,6 +535,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true;
}
+ listen = async () => {
+ let transcript = await DictationManager.Instance.listen();
+ transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
+ }
+
@action
onContextMenu = async (e: React.MouseEvent): Promise<void> => {
e.persist();
@@ -558,8 +563,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" });
cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" });
cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+ cm.addItem({ description: "Transcribe Speech", event: this.listen, icon: "microphone" });
let makes: ContextMenuProps[] = [];
- makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
+ makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" });
makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" });
makes.push({
description: "Make Portal", event: () => {
@@ -576,12 +582,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}, icon: "window-restore"
});
cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" });
- // cm.addItem({
- // description: "Find aliases", event: async () => {
- // const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
- // this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
- // }, icon: "search"
- // });
if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
}
@@ -597,6 +597,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
}
+ cm.addItem({
+ description: "Download document", icon: "download", event: () => {
+ const a = document.createElement("a");
+ const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`);
+ a.href = url;
+ a.download = `DocExport-${this.props.Document[Id]}.zip`;
+ a.click();
+ }
+ });
cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
type User = { email: string, userDocumentId: string };
let usersMenu: ContextMenuProps[] = [];
@@ -690,7 +699,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
<div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
- pointerEvents: this.layoutDoc.isBackground ? "none" : "all",
+ pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
color: foregroundColor,
outlineColor: "maroon",
outlineStyle: "dashed",
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index a24abb32e..247f7d1ea 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -43,7 +43,7 @@
.formattedTextBox-inner-rounded div,
.formattedTextBox-inner div {
- padding: 10px;
+ padding: 10px 10px;
}
.menuicon {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index fc0cc98aa..e076efe18 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,10 +1,11 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction, computed, trace } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
+import { Node as ProsNode } from "prosemirror-model";
import { EditorState, Plugin, Transaction, Selection } from "prosemirror-state";
import { NodeType, Slice, Node, Fragment } from 'prosemirror-model';
import { EditorView } from "prosemirror-view";
@@ -33,6 +34,7 @@ import { Templates } from '../Templates';
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
+import { For } from 'babel-types';
import { DateField } from '../../../new_fields/DateField';
import { Utils } from '../../../Utils';
import { MainOverlayTextBox } from '../MainOverlayTextBox';
@@ -49,6 +51,7 @@ export interface FormattedTextBoxProps {
height?: string;
color?: string;
outer_div?: (domminus: HTMLElement) => void;
+ firstinstance?: boolean;
}
const richTextSchema = createSchema({
@@ -63,14 +66,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
public static LayoutString(fieldStr: string = "data") {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
+ public static Instance: FormattedTextBox;
private _ref: React.RefObject<HTMLDivElement>;
private _outerdiv?: (dominus: HTMLElement) => void;
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
- private _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
private _applyingChange: boolean = false;
private _linkClicked = "";
private _reactionDisposer: Opt<IReactionDisposer>;
+ private _searchReactionDisposer?: Lambda;
private _textReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
@@ -100,6 +105,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return "";
}
+ public static getToolTip() {
+ return this._toolTipTextMenu;
+ }
+
@undoBatch
public setFontColor(color: string) {
let self = this;
@@ -115,6 +124,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
constructor(props: FieldViewProps) {
super(props);
+ //if (this.props.firstinstance) {
+ FormattedTextBox.Instance = this;
+ //}
if (this.props.outer_div) {
this._outerdiv = this.props.outer_div;
}
@@ -129,7 +141,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
@computed get extensionDoc() { return Doc.resolvedFieldDataDoc(this.dataDoc, this.props.fieldKey, "dummy"); }
- @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+ @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); }
+
paste = (e: ClipboardEvent) => {
if (e.clipboardData && this._editorView) {
@@ -145,7 +158,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
// tx.setSelection(new Selection(tx.))
const state = this._editorView!.state;
this._editorView!.dispatch(tx);
- if (this._toolTipTextMenu) {
+ if (FormattedTextBox._toolTipTextMenu) {
// this._toolTipTextMenu.makeLinkWithState(state)
}
e.stopPropagation();
@@ -161,7 +174,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this._editorView) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
+ if (state.selection.empty && FormattedTextBox._toolTipTextMenu) {
+ const marks = tx.storedMarks;
+ if (marks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(marks); }
+ }
this._applyingChange = true;
+ const fieldkey = "preview";
+ if (Object.keys(this.dataDoc).indexOf(fieldkey) !== -1) {
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ this.dataDoc[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+ }
+ else {
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+ }
if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
@@ -175,6 +201,50 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ public highlightSearchTerms = (terms: String[]) => {
+ if (this._editorView && (this._editorView as any).docView) {
+ const fieldkey = "preview";
+ const doc = this._editorView.state.doc;
+ const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
+ if (node.isLeaf && node.isText && node.text) {
+ let nodeText: String = node.text;
+ let tokens = nodeText.split(" ");
+ let start = pos;
+ tokens.forEach((word) => {
+ if (terms.includes(word) && this._editorView) {
+ this._editorView.dispatch(this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark));
+ // else {
+ // this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark);
+ // }
+ }
+ start += word.length + 1;
+ });
+ }
+ });
+ }
+ }
+
+ public unhighlightSearchTerms = () => {
+ if (this._editorView && (this._editorView as any).docView) {
+ const doc = this._editorView.state.doc;
+ const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
+ if (node.isLeaf && node.isText && node.text) {
+ if (node.marks.includes(mark) && this._editorView) {
+ this._editorView.dispatch(this._editorView.state.tr.removeMark(pos, pos + node.nodeSize, mark));
+ }
+ }
+ });
+ // const fieldkey = 'search_string';
+ // if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
+ // this.props.Document[fieldkey] = undefined;
+ // }
+ // else this.props.Document.proto![fieldkey] = undefined;
+ // }
+ }
+ }
+
protected createDropTarget = (ele: HTMLDivElement) => {
this._proseRef = ele;
if (this.dropDisposer) {
@@ -277,6 +347,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}, { fireImmediately: true });
this.setupEditor(config, this.dataDoc, this.props.fieldKey);
+
+ this._searchReactionDisposer = reaction(() => {
+ return StrCast(this.props.Document.search_string);
+ }, searchString => {
+ const fieldkey = 'preview';
+ let preview = false;
+ // if (!this._editorView && Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
+ // preview = true;
+ // }
+ if (searchString) {
+ this.highlightSearchTerms([searchString]);
+ }
+ else {
+ this.unhighlightSearchTerms();
+ }
+ }, { fireImmediately: true });
}
clipboardTextSerializer = (slice: Slice): string => {
@@ -411,7 +497,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
- if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+ if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
//this._toolTipTextMenu.tooltip.style.opacity = "0";
}
}
@@ -467,7 +553,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
onPointerUp = (e: React.PointerEvent): void => {
- if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+ if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
//this._toolTipTextMenu.tooltip.style.opacity = "1";
}
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
@@ -508,7 +594,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
tooltipTextMenuPlugin() {
let myprops = this.props;
- let self = this;
+ let self = FormattedTextBox;
return new Plugin({
view(_editorView) {
return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index dbe545048..9a0615d68 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -68,7 +68,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
private dropDisposer?: DragManager.DragDropDisposer;
- @computed get dataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+ @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : this.props.Document; }
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -93,17 +93,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
if (de.data instanceof DragManager.DocumentDragData) {
de.data.droppedDocuments.forEach(action((drop: Doc) => {
if (de.mods === "CtrlKey") {
- let temp = Doc.MakeDelegate(drop);
- this.props.Document.nativeWidth = Doc.GetProto(this.props.Document).nativeWidth = undefined;
- this.props.Document.nativeHeight = Doc.GetProto(this.props.Document).nativeHeight = undefined;
- this.props.Document.width = drop.width;
- this.props.Document.height = drop.height;
- Doc.GetProto(this.props.Document).type = DocumentType.TEMPLATE;
- if (this.props.DataDoc && this.props.DataDoc.layout === this.props.Document) {
- this.props.DataDoc.layout = temp;
- } else {
- this.props.Document.layout = temp;
- }
+ Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc);
e.stopPropagation();
} else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) {
Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url);
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index 0ea948c81..ecb3e9db4 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -290,7 +290,7 @@ export class LinkGroupEditor extends React.Component<LinkGroupEditorProps> {
let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
let index = keys.indexOf("");
if (index > -1) keys.splice(index, 1);
- let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c));
+ let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
let ref = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/nodes/LinkMenuGroup.tsx b/src/client/views/nodes/LinkMenuGroup.tsx
index 0cb216aa6..e04044266 100644
--- a/src/client/views/nodes/LinkMenuGroup.tsx
+++ b/src/client/views/nodes/LinkMenuGroup.tsx
@@ -72,7 +72,7 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
let keys = LinkManager.Instance.getMetadataKeysInGroup(groupType);
let index = keys.indexOf("");
if (index > -1) keys.splice(index, 1);
- let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c));
+ let cols = ["anchor1", "anchor2", ...[...keys]].map(c => new SchemaHeaderField(c, "#f1efeb"));
let docs: Doc[] = LinkManager.Instance.getAllMetadataDocsInGroup(groupType);
let createTable = action(() => Docs.Create.SchemaDocument(cols, docs, { width: 500, height: 300, title: groupType + " table" }));
let ref = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1f8636826..704030d85 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -23,6 +23,7 @@ import { faVideo } from "@fortawesome/free-solid-svg-icons";
import { CompileScript } from "../../util/Scripting";
import { Doc } from "../../../new_fields/Doc";
import { ScriptField } from "../../../new_fields/ScriptField";
+var path = require('path');
type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const VideoDocument = makeInterface(positionSchema, pageSchema);
@@ -89,6 +90,13 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
this._youtubePlayer && this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab");
}
+ choosePath(url: string) {
+ if (url.indexOf(window.location.origin) === -1) {
+ return Utils.CorsProxy(url);
+ }
+ return url;
+ }
+
@action public Snapshot() {
let width = NumCast(this.props.Document.width);
let height = NumCast(this.props.Document.height);
@@ -127,7 +135,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
if (returnedFilename) {
- let url = Utils.prepend(returnedFilename);
+ let url = this.choosePath(Utils.prepend(returnedFilename));
let imageSummary = Docs.Create.ImageDocument(url, {
x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss
index 97cbd4a24..65b09c833 100644
--- a/src/client/views/presentationview/PresentationView.scss
+++ b/src/client/views/presentationview/PresentationView.scss
@@ -6,6 +6,8 @@
right: 0;
top: 0;
bottom: 0;
+ letter-spacing: 2px;
+
}
.presentationView-item {
@@ -19,13 +21,11 @@
-ms-user-select: none;
user-select: none;
transition: all .1s;
- //max-height: 250px;
.documentView-node {
- // height: auto !important;
- // width: aut !important;
+
position: absolute;
z-index: 1;
}
@@ -52,10 +52,11 @@
.presentationView-selected {
background: gray;
+ color: black;
}
.presentationView-heading {
- background: lightseagreen;
+ background: gray;
padding: 10px;
display: inline-block;
width: 100%;
@@ -67,6 +68,8 @@
font-size: 25px;
display: inline-block;
width: calc(100% - 200px);
+ letter-spacing: 2px;
+
}
.presentation-icon {
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index 4fe9d3a1b..bea70f00b 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -375,7 +375,7 @@ export class PresentationView extends React.Component<PresViewProps> {
//awaiting jump so that new scale can be found, since jumping is async
await DocumentManager.Instance.jumpToDocument(curDoc, true);
} else {
- await DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
+ await DocumentManager.Instance.jumpToDocument(curDoc, true, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined));
}
let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc);
@@ -881,24 +881,15 @@ export class PresentationView extends React.Component<PresViewProps> {
if (presWidth - presMinWidth !== 0) {
this.curPresentation.width = 0;
}
+ if (presWidth === 0) {
+ this.curPresentation.width = presMinWidth;
+ }
}
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
}
/**
- * This function gets triggered on click of the dragger. It opens up the
- * presentation view, if it was closed beforehand.
- */
- togglePresView = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- let width = NumCast(this.curPresentation.width);
- if (width === 0) {
- this.curPresentation.width = presMinWidth;
- }
- }
- /**
* This function is a setter that opens up the
* presentation mode, by setting it's render flag
* to true. It also closes the presentation view.
@@ -992,7 +983,7 @@ export class PresentationView extends React.Component<PresViewProps> {
</div>
<div className="mainView-libraryHandle"
style={{ cursor: "ew-resize", right: `${width - 10}px`, backgroundColor: "white", opacity: this.opacity, transition: "0.7s opacity ease" }}
- onPointerDown={this.onPointerDown} onClick={this.togglePresView}>
+ onPointerDown={this.onPointerDown}>
<span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} />
</div>
{this.renderPresMode()}
diff --git a/src/client/views/search/CheckBox.scss b/src/client/views/search/CheckBox.scss
index af59d5fbf..cc858bec6 100644
--- a/src/client/views/search/CheckBox.scss
+++ b/src/client/views/search/CheckBox.scss
@@ -13,9 +13,9 @@
margin-top: 0px;
.check-container:hover~.check-box {
- background-color: $intermediate-color;
+ background-color: $darker-alt-accent;
}
-
+
.check-container {
width: 40px;
height: 40px;
@@ -27,7 +27,8 @@
position: absolute;
fill-opacity: 0;
stroke-width: 4px;
- stroke: white;
+ // stroke: white;
+ stroke: gray;
}
}
@@ -55,5 +56,4 @@
margin-left: 15px;
}
-}
-
+} \ No newline at end of file
diff --git a/src/client/views/search/FieldFilters.scss b/src/client/views/search/FieldFilters.scss
index ba0926140..e1d0d8df5 100644
--- a/src/client/views/search/FieldFilters.scss
+++ b/src/client/views/search/FieldFilters.scss
@@ -1,5 +1,12 @@
.field-filters {
width: 100%;
display: grid;
- grid-template-columns: 18% 20% 60%;
+ // 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/FilterBox.scss b/src/client/views/search/FilterBox.scss
index 1eb8963d7..ebb39460d 100644
--- a/src/client/views/search/FilterBox.scss
+++ b/src/client/views/search/FilterBox.scss
@@ -3,22 +3,25 @@
.filter-form {
padding: 25px;
- width: 600px;
- background: $dark-color;
+ width: 440px;
+ background: whitesmoke;
position: relative;
right: 1px;
- color: $light-color;
+ color: grey;
flex-direction: column;
display: inline-block;
transform-origin: top;
overflow: auto;
+ border-radius: 15px;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+ border: solid #BBBBBBBB 1px;
.top-filter-header {
#header {
text-transform: uppercase;
letter-spacing: 2px;
- font-size: 25;
+ font-size: 13;
width: 80%;
}
@@ -26,13 +29,13 @@
width: 20%;
opacity: .6;
position: relative;
- display: inline-block;
+ display: block;
.line {
display: block;
background: $alt-accent;
- width: $width-line;
- height: $height-line;
+ width: 20;
+ height: 3;
position: absolute;
right: 0;
border-radius: ($height-line / 2);
@@ -69,9 +72,10 @@
display: flex;
align-items: center;
margin-bottom: 10px;
+ letter-spacing: 2px;
.filter-title {
- font-size: 18;
+ font-size: 13;
text-transform: uppercase;
margin-top: 10px;
margin-bottom: 10px;
@@ -96,6 +100,7 @@
-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;
}
}
}
@@ -105,4 +110,72 @@
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
index 706d1eb7f..995ddd5c3 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -2,25 +2,26 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
import "./SearchBox.scss";
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core';
import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { DocumentType } from '../../documents/Documents';
import { Cast, StrCast } from '../../../new_fields/Types';
import * as _ from "lodash";
-import { ToggleBar } from './ToggleBar';
import { IconBar } from './IconBar';
import { FieldFilters } from './FieldFilters';
import { SelectionManager } from '../../util/SelectionManager';
import { DocumentView } from '../nodes/DocumentView';
import { CollectionFilters } from './CollectionFilters';
-import { NaviconButton } from './NaviconButton';
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",
@@ -35,11 +36,18 @@ export class FilterBox extends React.Component {
public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.HIST, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, 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;
@@ -114,10 +122,9 @@ export class FilterBox extends React.Component {
@action.bound
resetFilters = () => {
- ToggleBar.Instance.resetToggle();
+ this._basicWordStatus = true;
IconBar.Instance.selectAll();
FieldFilters.Instance.resetFieldFilters();
- CollectionFilters.Instance.resetCollectionFilters();
}
basicRequireWords(query: string): string {
@@ -259,6 +266,40 @@ export class FilterBox extends React.Component {
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;
@@ -268,10 +309,9 @@ export class FilterBox extends React.Component {
//if true, any keywords can be used. if false, all keywords are required.
@action.bound
- handleWordQueryChange = () => { this._basicWordStatus = !this._basicWordStatus; }
-
- @action.bound
- getBasicWordStatus() { return this._basicWordStatus; }
+ handleWordQueryChange = () => {
+ this._basicWordStatus = !this._basicWordStatus;
+ }
@action.bound
updateIcon(newArray: string[]) { this._icons = newArray; }
@@ -290,16 +330,10 @@ export class FilterBox extends React.Component {
}
@action.bound
- toggleFieldOpen() { this._fieldOpen = !this._fieldOpen; }
-
- @action.bound
- toggleColOpen() { this._colOpen = !this._colOpen; }
-
- @action.bound
- toggleTypeOpen() { this._typeOpen = !this._typeOpen; }
+ updateAnyKeywordStatus(newStat: boolean) { this._anyKeywordStatus = newStat; }
@action.bound
- toggleWordStatusOpen() { this._wordStatusOpen = !this._wordStatusOpen; }
+ updateAllKeywordStatus(newStat: boolean) { this._allKeywordStatus = newStat; }
@action.bound
updateTitleStatus(newStat: boolean) { this._titleFieldStatus = newStat; }
@@ -319,6 +353,8 @@ export class FilterBox extends React.Component {
@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; }
@@ -326,6 +362,31 @@ export class FilterBox extends React.Component {
getAuthorStatus() { return this._authorFieldStatus; }
getDataStatus() { return this._deletedDocsStatus; }
+ getActiveFilters() {
+ console.log(this._authorFieldStatus, this._titleFieldStatus, this._dataFieldStatus);
+ 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
@@ -334,11 +395,13 @@ export class FilterBox extends React.Component {
<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" } : { 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>
@@ -347,33 +410,20 @@ export class FilterBox extends React.Component {
<div className="filter-div">
<div className="filter-header">
<div className='filter-title words'>Required words</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleWordStatusOpen} /></div>
</div>
<div className="filter-panel" >
- <ToggleBar handleChange={this.handleWordQueryChange} getStatus={this.getBasicWordStatus}
- originalStatus={this._basicWordStatus} optionOne={"Include Any Keywords"} optionTwo={"Include All Keywords"} />
+ <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 style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleTypeOpen} /></div>
</div>
<div className="filter-panel"><IconBar /></div>
</div>
<div className="filter-div">
<div className="filter-header">
- <div className='filter-title collection'>Search in current collections</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleColOpen} /></div>
- </div>
- <div className="filter-panel"><CollectionFilters
- updateCollectionStatus={this.updateCollectionStatus} updateParentCollectionStatus={this.updateParentCollectionStatus} updateSelfCollectionStatus={this.updateSelfCollectionStatus}
- collectionStatus={this._collectionStatus} collectionParentStatus={this._collectionParentStatus} collectionSelfStatus={this._collectionSelfStatus} /></div>
- </div>
- <div className="filter-div">
- <div className="filter-header">
<div className="filter-title field">Filter by Basic Keys</div>
- <div style={{ marginLeft: "auto" }}><NaviconButton onClick={this.toggleFieldOpen} /></div>
</div>
<div className="filter-panel"><FieldFilters
titleFieldStatus={this._titleFieldStatus} dataFieldStatus={this._deletedDocsStatus} authorFieldStatus={this._authorFieldStatus}
@@ -381,13 +431,12 @@ export class FilterBox extends React.Component {
</div>
</div>
<div className="filter-buttons" style={{ display: "flex", justifyContent: "space-around" }}>
- <button className="minimize-filter" onClick={this.minimizeAll}>Minimize All</button>
- <button className="advanced-filter" >Advanced Filters</button>
<button className="save-filter" >Save Filters</button>
<button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button>
</div>
</div>
- ) : undefined}
+ ) :
+ undefined}
</div>
);
}
diff --git a/src/client/views/search/IconBar.scss b/src/client/views/search/IconBar.scss
index e384722ce..2555ad271 100644
--- a/src/client/views/search/IconBar.scss
+++ b/src/client/views/search/IconBar.scss
@@ -4,9 +4,8 @@
display: flex;
justify-content: space-evenly;
align-items: center;
- height: 40px;
+ height: 35px;
width: 100%;
flex-wrap: wrap;
margin-bottom: 10px;
-}
-
+} \ No newline at end of file
diff --git a/src/client/views/search/IconButton.scss b/src/client/views/search/IconButton.scss
index 94b294ba5..d1853177e 100644
--- a/src/client/views/search/IconButton.scss
+++ b/src/client/views/search/IconButton.scss
@@ -4,13 +4,15 @@
display: flex;
flex-direction: column;
align-items: center;
- width: 45px;
+ width: 30px;
height: 60px;
.type-icon {
- height: 45px;
- width: 45px;
+ height: 30px;
+ width: 30px;
color: $light-color;
+ // background-color: rgb(194, 194, 197);
+ background-color: gray;
border-radius: 50%;
display: flex;
justify-content: center;
@@ -22,8 +24,8 @@
font-size: 2em;
.fontawesome-icon {
- height: 24px;
- width: 24px;
+ height: 15px;
+ width: 15px
}
}
@@ -44,7 +46,7 @@
transform: scale(1.1);
background-color: $darker-alt-accent;
opacity: 1;
-
+
+.filter-description {
opacity: 1;
}
diff --git a/src/client/views/search/IconButton.tsx b/src/client/views/search/IconButton.tsx
index bfe2c7d0b..5d23f6eeb 100644
--- a/src/client/views/search/IconButton.tsx
+++ b/src/client/views/search/IconButton.tsx
@@ -13,6 +13,7 @@ import { IconBar } from './IconBar';
import { props } from 'bluebird';
import { FilterBox } from './FilterBox';
import { Search } from '../../../server/Search';
+import { gravity } from 'sharp';
library.add(faSearch);
library.add(faObjectGroup);
@@ -123,11 +124,11 @@ export class IconButton extends React.Component<IconButtonProps>{
selected = {
opacity: 1,
- backgroundColor: "#c2c2c5" //$alt-accent
+ backgroundColor: "rgb(128, 128, 128)"
};
notSelected = {
- opacity: 0.6,
+ opacity: 0.2,
};
hoverStyle = {
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 109b88ac9..fcdc79220 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -45,6 +45,11 @@
top: 300px;
display: flex;
flex-direction: column;
+ margin-right: 72px;
+ // height: 560px;
+ height: 100%;
+ // overflow: hidden;
+ // overflow-y: auto;
max-height: 560px;
overflow: hidden;
overflow-y: auto;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 562594210..0390359b3 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -21,11 +21,18 @@ import { DocumentView } from "../nodes/DocumentView";
import { SearchBox } from "./SearchBox";
import "./SearchItem.scss";
import "./SelectorContextMenu.scss";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { ObjectField } from "../../../new_fields/ObjectField";
import { ContextMenu } from "../ContextMenu";
import { faFile } from '@fortawesome/free-solid-svg-icons';
+import { DocServer } from "../../DocServer";
export interface SearchItemProps {
doc: Doc;
+ query?: string;
highlighting: string[];
}
@@ -86,7 +93,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
SetupDrag(item, () => doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}>
<FontAwesomeIcon icon={faStickyNote} />
</div>
- <a className="title" onClick={this.getOnClick(doc)}>{doc.col.title}</a>
+ <a onClick={this.getOnClick(doc)}>{doc.col.title}</a>
</div>;
})}
</div>
@@ -94,27 +101,115 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
}
}
+export interface LinkMenuProps {
+ doc1: Doc;
+ doc2: Doc;
+}
+
+@observer
+export class LinkContextMenu extends React.Component<LinkMenuProps> {
+
+ highlightDoc = (doc: Doc) => {
+ return () => {
+ doc.libraryBrush = true;
+ };
+ }
+
+ unHighlightDoc = (doc: Doc) => {
+ return () => {
+ doc.libraryBrush = false;
+ };
+ }
+
+ getOnClick(col: Doc) {
+ return () => {
+ CollectionDockingView.Instance.AddRightSplit(col, undefined);
+ };
+ }
+
+ 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;
+ private _previewDoc?: Doc;
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);
+ if (this.props.doc.data instanceof RichTextField) {
+ this.highlightTextBox(this.props.doc);
+ }
// CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
}
@observable _useIcons = true;
@observable _displayDim = 50;
- @computed
- public get DocumentIcon() {
+ highlightTextBox = (doc: Doc) => {
+ if (this.props.query) {
+ const fieldkey = 'search_string';
+ if (Object.keys(doc).indexOf(fieldkey) === -1) {
+ doc.search_string = this.props.query;
+ }
+ else {
+ doc.search_string = undefined;
+ }
+
+ }
+ }
+
+ fitToBox = () => {
+ let bounds = Doc.ComputeContentBounds([this.props.doc]);
+ return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim];
+ }
+
+ componentWillUnmount() {
+ if (this._previewDoc) {
+ DocServer.DeleteDocument(this._previewDoc[Id]);
+ }
+ }
+
+
+ //@computed
+ @action
+ public DocumentIcon() {
+ let layoutresult = StrCast(this.props.doc.type);
if (!this._useIcons) {
+ let renderDoc = this.props.doc;
+ //let box: number[] = [];
+ if (layoutresult.indexOf(DocumentType.COL) !== -1) {
+ renderDoc = Doc.MakeDelegate(renderDoc);
+ let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
+ var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
+ let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+ return {
+ x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y),
+ r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b)
+ };
+ }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+ let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim];
+ }
let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
let returnYDimension = () => this._displayDim;
- let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension());
- return <div
- onPointerDown={action(() => { this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); })}
+ let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
+ let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+ this._previewDoc = newRenderDoc;
+ 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))}
onPointerLeave={action(() => this._displayDim = 50)} >
<DocumentView
@@ -138,9 +233,15 @@ export class SearchItem extends React.Component<SearchItemProps> {
ContentScaling={scale}
/>
</div>;
+ const data = renderDoc.data;
+ if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
+ newRenderDoc.preview = true;
+ newRenderDoc.search_string = this.props.query;
+ return docview;
+ }
+ if (this._previewDoc) {
+ DocServer.DeleteDocument(this._previewDoc[Id]);
}
-
- let layoutresult = StrCast(this.props.doc.type);
let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
@@ -239,6 +340,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
}
render() {
+ const doc1 = Cast(this.props.doc.anchor1, Doc);
+ const doc2 = Cast(this.props.doc.anchor2, Doc);
return (
<div className="search-overview" onPointerDown={this.pointerDown} onContextMenu={this.onContextMenu}>
<div className="search-item" onPointerEnter={this.highlightDoc} onPointerLeave={this.unHighlightDoc} id="result"
@@ -262,7 +365,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
</div>
</div>
<div className="searchBox-instances">
- <SelectorContextMenu {...this.props} />
+ {(doc1 instanceof Doc && doc2 instanceof Doc) ? this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={doc1} doc2={doc2} /> :
+ <SelectorContextMenu {...this.props} /> : null}
</div>
</div>
);
diff --git a/src/client/views/search/SelectorContextMenu.scss b/src/client/views/search/SelectorContextMenu.scss
index 49f77b9bf..48cacc608 100644
--- a/src/client/views/search/SelectorContextMenu.scss
+++ b/src/client/views/search/SelectorContextMenu.scss
@@ -3,6 +3,7 @@
.parents {
background: $lighter-alt-accent;
padding: 10px;
+ // width: 300px;
.contexts {
text-transform: uppercase;
diff --git a/src/client/views/search/ToggleBar.scss b/src/client/views/search/ToggleBar.scss
index 633a194fe..79f866acb 100644
--- a/src/client/views/search/ToggleBar.scss
+++ b/src/client/views/search/ToggleBar.scss
@@ -16,11 +16,15 @@
-moz-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
+ color: gray;
+ font-size: 13;
}
}
.toggle-bar {
- height: 50px;
+ // height: 50px;
+ height: 30px;
+ width: 100px;
background-color: $alt-accent;
border-radius: 10px;
padding: 5px;
@@ -28,7 +32,8 @@
align-items: center;
.toggle-button {
- width: 275px;
+ // width: 275px;
+ width: 40px;
height: 100%;
border-radius: 10px;
background-color: $light-color;
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index 178578c5c..a30104089 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -59,6 +59,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
this._forwardTimeline.play();
this._forwardTimeline.reverse();
this.props.handleChange();
+ console.log(this.props.getStatus())
}
@action.bound
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 2b3eed154..24db3f934 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -10,6 +10,7 @@ import { List } from '../new_fields/List';
import { URLField } from '../new_fields/URLField';
import { EditableView } from '../client/views/EditableView';
import { CompileScript } from '../client/util/Scripting';
+import { RichTextField } from '../new_fields/RichTextField';
import { DateField } from '../new_fields/DateField';
import { ScriptField } from '../new_fields/ScriptField';
import CursorField from '../new_fields/CursorField';
@@ -126,6 +127,8 @@ class DebugViewer extends React.Component<{ field: FieldResult, setValue(value:
content = <p>"{field}"</p>;
} else if (typeof field === "number" || typeof field === "boolean") {
content = <p>{field}</p>;
+ } else if (field instanceof RichTextField) {
+ content = <p>RTF: {field.Data}</p>;
} else if (field instanceof URLField) {
content = <p>{field.url.href}</p>;
} else if (field instanceof Promise) {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 59314783b..84b8589dd 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -1,4 +1,4 @@
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
import { serializable, primitive, map, alias, list, PropSchema, custom } from "serializr";
import { autoObject, SerializationHelper, Deserializable, afterDocDeserialize } from "../client/util/SerializationHelper";
import { DocServer } from "../client/DocServer";
@@ -197,8 +197,12 @@ export namespace Doc {
}
export function Get(doc: Doc, key: string, ignoreProto: boolean = false): FieldResult {
- const self = doc[Self];
- return getField(self, key, ignoreProto);
+ try {
+ const self = doc[Self];
+ return getField(self, key, ignoreProto);
+ } catch {
+ return doc;
+ }
}
export function GetT<T extends Field>(doc: Doc, key: string, ctor: ToConstructor<T>, ignoreProto: boolean = false): FieldResult<T> {
return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult<T>;
@@ -462,6 +466,23 @@ export namespace Doc {
otherdoc.type = DocumentType.TEMPLATE;
return otherdoc;
}
+ export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetData?: Doc) {
+ let temp = Doc.MakeDelegate(templateDoc);
+ target.nativeWidth = Doc.GetProto(target).nativeWidth = undefined;
+ target.nativeHeight = Doc.GetProto(target).nativeHeight = undefined;
+ target.width = templateDoc.width;
+ target.height = templateDoc.height;
+ Doc.GetProto(target).type = DocumentType.TEMPLATE;
+ if (targetData && targetData.layout === target) {
+ targetData.layout = temp;
+ targetData.miniLayout = StrCast(templateDoc.miniLayout);
+ targetData.detailedLayout = targetData.layout;
+ } else {
+ target.layout = temp;
+ target.miniLayout = StrCast(templateDoc.miniLayout);
+ target.detailedLayout = target.layout;
+ }
+ }
export function MakeTemplate(fieldTemplate: Doc, metaKey: string, templateDataDoc: Doc) {
// move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
@@ -494,18 +515,29 @@ export namespace Doc {
setTimeout(() => fieldTemplate.proto = templateDataDoc);
}
- export async function ToggleDetailLayout(d: Doc) {
- let miniLayout = await PromiseValue(d.miniLayout);
- let detailLayout = await PromiseValue(d.detailedLayout);
- d.layout !== miniLayout ? miniLayout && (d.layout = d.miniLayout) : detailLayout && (d.layout = detailLayout);
- if (d.layout === detailLayout) Doc.GetProto(d).nativeWidth = Doc.GetProto(d).nativeHeight = undefined;
+ export function ToggleDetailLayout(d: Doc) {
+ runInAction(async () => {
+ let miniLayout = await PromiseValue(d.miniLayout);
+ let detailLayout = await PromiseValue(d.detailedLayout);
+ d.layout !== miniLayout ? miniLayout && (d.layout = d.miniLayout) : detailLayout && (d.layout = detailLayout);
+ if (d.layout === detailLayout) Doc.GetProto(d).nativeWidth = Doc.GetProto(d).nativeHeight = undefined;
+ });
}
- export async function UseDetailLayout(d: Doc) {
- let miniLayout = await PromiseValue(d.miniLayout);
- let detailLayout = await PromiseValue(d.detailedLayout);
- if (miniLayout && d.layout === miniLayout && detailLayout) {
- d.layout = detailLayout;
- d.nativeWidth = d.nativeHeight = undefined;
- }
+ export function UseDetailLayout(d: Doc) {
+ runInAction(async () => {
+ let detailLayout1 = await PromiseValue(d.detailedLayout);
+ let detailLayout = await PromiseValue(d.detailedLayout);
+ if (detailLayout) {
+ d.layout = detailLayout;
+ d.nativeWidth = d.nativeHeight = undefined;
+ if (detailLayout instanceof Doc) {
+ let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc;
+ d.layout = delegDetailLayout;
+ let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout);
+ let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout);
+ delegDetailLayout.layout = subDetailLayout;
+ }
+ }
+ });
}
} \ No newline at end of file
diff --git a/src/new_fields/ObjectField.ts b/src/new_fields/ObjectField.ts
index 5f4a6f8fb..65ada91c0 100644
--- a/src/new_fields/ObjectField.ts
+++ b/src/new_fields/ObjectField.ts
@@ -1,6 +1,7 @@
import { Doc } from "./Doc";
import { RefField } from "./RefField";
import { OnUpdate, Parent, Copy, ToScriptString } from "./FieldSymbols";
+import { Scripting } from "../client/util/Scripting";
export abstract class ObjectField {
protected [OnUpdate](diff?: any) { }
@@ -15,3 +16,5 @@ export namespace ObjectField {
return field[Copy]();
}
}
+
+Scripting.addGlobal(ObjectField); \ No newline at end of file
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 78a3a4067..89799b2af 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -20,6 +20,6 @@ export class RichTextField extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return `new RichTextField("${this.Data}")`;
}
} \ No newline at end of file
diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts
index d5da56b10..23605cfb0 100644
--- a/src/new_fields/SchemaHeaderField.ts
+++ b/src/new_fields/SchemaHeaderField.ts
@@ -6,7 +6,7 @@ import { scriptingGlobal, Scripting } from "../client/util/Scripting";
import { ColumnType } from "../client/views/collections/CollectionSchemaView";
export const PastelSchemaPalette = new Map<string, string>([
- ["pink1", "#FFB4E8"],
+ // ["pink1", "#FFB4E8"],
["pink2", "#ff9cee"],
["pink3", "#ffccf9"],
["pink4", "#fcc2ff"],
@@ -15,7 +15,7 @@ export const PastelSchemaPalette = new Map<string, string>([
["purple2", "#c5a3ff"],
["purple3", "#d5aaff"],
["purple4", "#ecd4ff"],
- ["purple5", "#fb34ff"],
+ // ["purple5", "#fb34ff"],
["purple6", "#dcd3ff"],
["purple7", "#a79aff"],
["purple8", "#b5b9ff"],
@@ -25,17 +25,18 @@ export const PastelSchemaPalette = new Map<string, string>([
["bluegreen3", "#c4faf8"],
["bluegreen4", "#85e3ff"],
["bluegreen5", "#ace7ff"],
- ["bluegreen6", "#6eb5ff"],
+ // ["bluegreen6", "#6eb5ff"],
["bluegreen7", "#bffcc6"],
["bluegreen8", "#dbffd6"],
["yellow1", "#f3ffe3"],
["yellow2", "#e7ffac"],
["yellow3", "#ffffd1"],
["yellow4", "#fff5ba"],
- ["red1", "#ffc9de"],
+ // ["red1", "#ffc9de"],
["red2", "#ffabab"],
["red3", "#ffbebc"],
["red4", "#ffcbc1"],
+ ["orange1", "#ffd5b3"],
]);
export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)];
@@ -45,20 +46,23 @@ export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.
export class SchemaHeaderField extends ObjectField {
@serializable(primitive())
heading: string;
+ @serializable(primitive())
color: string;
+ @serializable(primitive())
type: number;
+ @serializable(primitive())
+ width: number;
+ @serializable(primitive())
+ desc: boolean | undefined; // boolean determines sort order, undefined when no sort
- constructor(heading: string = "", color?: string, type?: ColumnType) {
+ constructor(heading: string = "", color: string = RandomPastel(), type?: ColumnType, width?: number, desc?: boolean) {
super();
this.heading = heading;
- this.color = color === "" || color === undefined ? RandomPastel() : color;
- if (type) {
- this.type = type;
- }
- else {
- this.type = 0;
- }
+ this.color = color;
+ this.type = type ? type : 0;
+ this.width = width ? width : -1;
+ this.desc = desc;
}
setHeading(heading: string) {
@@ -76,6 +80,16 @@ export class SchemaHeaderField extends ObjectField {
this[OnUpdate]();
}
+ setWidth(width: number) {
+ this.width = width;
+ this[OnUpdate]();
+ }
+
+ setDesc(desc: boolean | undefined) {
+ this.desc = desc;
+ this[OnUpdate]();
+ }
+
[Copy]() {
return new SchemaHeaderField(this.heading, this.color, this.type);
}
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index b59ec9b9a..2ebfb9e71 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -88,6 +88,9 @@ export function setter(target: any, prop: string | symbol | number, value: any,
}
export function getter(target: any, prop: string | symbol | number, receiver: any): any {
+ if (prop === "then") {//If we're being awaited
+ return undefined;
+ }
if (typeof prop === "symbol") {
return target.__fields[prop] || target[prop];
}
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 1ff0e3b31..f0f45d8f9 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -236,7 +236,7 @@ def parse_document(file_name: str):
view_guids.append(write_image(pure_name, image))
copyfile(dir_path + "/" + image, dir_path +
"/" + image.replace(".", "_o.", 1))
- os.rename(dir_path + "/" + image, dir_path +
+ copyfile(dir_path + "/" + image, dir_path +
"/" + image.replace(".", "_m.", 1))
print(f"extracted {count} images...")
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 1c52a3f11..91d7ba87d 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -71,7 +71,7 @@ export class CurrentUserUtils {
doc.sidebar = sidebar;
}
StrCast(doc.title).indexOf("@") !== -1 && (doc.title = StrCast(doc.title).split("@")[0] + "'s Library");
-
+ doc.width = 100;
}
public static loadCurrentUser() {
diff --git a/src/server/database.ts b/src/server/database.ts
index 7f5331998..a7254fb0c 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -17,7 +17,7 @@ export class Database {
});
}
- public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) {
+ public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
if (this.db) {
let collection = this.db.collection(collectionName);
const prom = this.currentWrites[id];
@@ -30,7 +30,7 @@ export class Database {
delete this.currentWrites[id];
}
resolve();
- callback();
+ callback(err, res);
});
});
};
@@ -41,6 +41,30 @@ export class Database {
}
}
+ public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) {
+ if (this.db) {
+ let collection = this.db.collection(collectionName);
+ const prom = this.currentWrites[id];
+ let newProm: Promise<void>;
+ const run = (): Promise<void> => {
+ return new Promise<void>(resolve => {
+ collection.replaceOne({ _id: id }, value, { upsert }
+ , (err, res) => {
+ if (this.currentWrites[id] === newProm) {
+ delete this.currentWrites[id];
+ }
+ resolve();
+ callback(err, res);
+ });
+ });
+ };
+ newProm = prom ? prom.then(run) : run();
+ this.currentWrites[id] = newProm;
+ } else {
+ this.onConnect.push(() => this.replace(id, value, callback, upsert, collectionName));
+ }
+ }
+
public delete(query: any, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
public delete(id: string, collectionName?: string): Promise<mongodb.DeleteWriteOpResultObject>;
public delete(id: any, collectionName = Database.DocumentsCollection) {
@@ -126,6 +150,34 @@ export class Database {
}
}
+ public async visit(ids: string[], fn: (result: any) => string[], collectionName = "newDocuments"): Promise<void> {
+ if (this.db) {
+ const visited = new Set<string>();
+ while (ids.length) {
+ const count = Math.min(ids.length, 1000);
+ const index = ids.length - count;
+ const fetchIds = ids.splice(index, count).filter(id => !visited.has(id));
+ if (!fetchIds.length) {
+ continue;
+ }
+ const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res, "newDocuments"));
+ for (const doc of docs) {
+ const id = doc.id;
+ visited.add(id);
+ ids.push(...fn(doc));
+ }
+ }
+
+ } else {
+ return new Promise(res => {
+ this.onConnect.push(() => {
+ this.visit(ids, fn, collectionName);
+ res();
+ });
+ });
+ }
+ }
+
public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = "newDocuments"): Promise<mongodb.Cursor> {
if (this.db) {
let cursor = this.db.collection(collectionName).find(query);
diff --git a/src/server/index.ts b/src/server/index.ts
index 080b50ada..10a84c823 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -27,6 +27,7 @@ import { Client } from './Client';
import { Database } from './database';
import { MessageStore, Transferable, Types, Diff, YoutubeQueryTypes as YoutubeQueryType, YoutubeQueryInput } from "./Message";
import { RouteStore } from './RouteStore';
+import v4 = require('uuid/v4');
const app = express();
const config = require('../../webpack.config');
import { createCanvas, loadImage, Canvas } from "canvas";
@@ -39,11 +40,16 @@ import c = require("crypto");
import { Search } from './Search';
import { debug } from 'util';
import _ = require('lodash');
+import * as Archiver from 'archiver';
+import * as AdmZip from 'adm-zip';
import * as YoutubeApi from './youtubeApi/youtubeApiSample.js';
import { Response } from 'express-serve-static-core';
+import { DocComponent } from '../client/views/DocComponent';
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
const probe = require("probe-image-size");
+var SolrNode = require('solr-node');
+var shell = require('shelljs');
const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
let youtubeApiKey: string;
@@ -153,6 +159,7 @@ app.get("/version", (req, res) => {
});
// SEARCH
+const solrURL = "http://localhost:8983/solr/#/dash";
// GETTERS
@@ -180,6 +187,186 @@ function msToTime(duration: number) {
return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds;
}
+async function getDocs(id: string) {
+ const files = new Set<string>();
+ const docs: { [id: string]: any } = {};
+ const fn = (doc: any): string[] => {
+ const id = doc.id;
+ if (typeof id === "string" && id.endsWith("Proto")) {
+ //Skip protos
+ return [];
+ }
+ const ids: string[] = [];
+ for (const key in doc.fields) {
+ if (!doc.fields.hasOwnProperty(key)) {
+ continue;
+ }
+ const field = doc.fields[key];
+ if (field === undefined || field === null) {
+ continue;
+ }
+
+ if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+ ids.push(field.fieldId);
+ } else if (field.__type === "script" || field.__type === "computed") {
+ if (field.captures) {
+ ids.push(field.captures.fieldId);
+ }
+ } else if (field.__type === "list") {
+ ids.push(...fn(field));
+ } else if (typeof field === "string") {
+ const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g;
+ let match: string[] | null;
+ while ((match = re.exec(field)) !== null) {
+ ids.push(match[1]);
+ }
+ } else if (field.__type === "RichTextField") {
+ const re = /"href"\s*:\s*"(.*?)"/g;
+ let match: string[] | null;
+ while ((match = re.exec(field.Data)) !== null) {
+ const urlString = match[1];
+ const split = new URL(urlString).pathname.split("doc/");
+ if (split.length > 1) {
+ ids.push(split[split.length - 1]);
+ }
+ }
+ const re2 = /"src"\s*:\s*"(.*?)"/g;
+ while ((match = re2.exec(field.Data)) !== null) {
+ const urlString = match[1];
+ const pathname = new URL(urlString).pathname;
+ files.add(pathname);
+ }
+ } else if (["audio", "image", "video", "pdf", "web"].includes(field.__type)) {
+ const url = new URL(field.url);
+ const pathname = url.pathname;
+ files.add(pathname);
+ }
+ }
+
+ if (doc.id) {
+ docs[doc.id] = doc;
+ }
+ return ids;
+ };
+ await Database.Instance.visit([id], fn);
+ return { id, docs, files };
+}
+app.get("/serializeDoc/:docId", async (req, res) => {
+ const { docs, files } = await getDocs(req.params.docId);
+ res.send({ docs, files: Array.from(files) });
+});
+
+app.get("/downloadId/:docId", async (req, res) => {
+ res.set('Content-disposition', `attachment;`);
+ res.set('Content-Type', "application/zip");
+ const { id, docs, files } = await getDocs(req.params.docId);
+ const docString = JSON.stringify({ id, docs });
+ const zip = Archiver('zip');
+ zip.pipe(res);
+ zip.append(docString, { name: "doc.json" });
+ files.forEach(val => {
+ zip.file(__dirname + RouteStore.public + val, { name: val.substring(1) });
+ });
+ zip.finalize();
+});
+
+app.post("/uploadDoc", (req, res) => {
+ let form = new formidable.IncomingForm();
+ form.keepExtensions = true;
+ // let path = req.body.path;
+ const ids: { [id: string]: string } = {};
+ let remap = true;
+ const getId = (id: string): string => {
+ if (!remap) return id;
+ if (id.endsWith("Proto")) return id;
+ if (id in ids) {
+ return ids[id];
+ } else {
+ return ids[id] = v4();
+ }
+ };
+ const mapFn = (doc: any) => {
+ if (doc.id) {
+ doc.id = getId(doc.id);
+ }
+ for (const key in doc.fields) {
+ if (!doc.fields.hasOwnProperty(key)) {
+ continue;
+ }
+ const field = doc.fields[key];
+ if (field === undefined || field === null) {
+ continue;
+ }
+
+ if (field.__type === "proxy" || field.__type === "prefetch_proxy") {
+ field.fieldId = getId(field.fieldId);
+ } else if (field.__type === "script" || field.__type === "computed") {
+ if (field.captures) {
+ field.captures.fieldId = getId(field.captures.fieldId);
+ }
+ } else if (field.__type === "list") {
+ mapFn(field);
+ } else if (typeof field === "string") {
+ const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g;
+ doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => {
+ return `${p1}${getId(p2)}"`;
+ });
+ } else if (field.__type === "RichTextField") {
+ const re = /("href"\s*:\s*")(.*?)"/g;
+ field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => {
+ return `${p1}${getId(p2)}"`;
+ });
+ }
+ }
+ };
+ form.parse(req, async (err, fields, files) => {
+ remap = fields.remap !== "false";
+ let id: string = "";
+ try {
+ for (const name in files) {
+ const path_2 = files[name].path;
+ const zip = new AdmZip(path_2);
+ zip.getEntries().forEach(entry => {
+ if (!entry.entryName.startsWith("files/")) return;
+ let dirname = path.dirname(entry.entryName) + "/";
+ let extname = path.extname(entry.entryName);
+ let basename = path.basename(entry.entryName).split(".")[0];
+ // zip.extractEntryTo(dirname + basename + "_o" + extname, __dirname + RouteStore.public, true, false);
+ // zip.extractEntryTo(dirname + basename + "_s" + extname, __dirname + RouteStore.public, true, false);
+ // zip.extractEntryTo(dirname + basename + "_m" + extname, __dirname + RouteStore.public, true, false);
+ // zip.extractEntryTo(dirname + basename + "_l" + extname, __dirname + RouteStore.public, true, false);
+ zip.extractEntryTo(entry.entryName, __dirname + RouteStore.public, true, false);
+ dirname = "/" + dirname;
+
+ fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_o" + extname));
+ fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_s" + extname));
+ fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_m" + extname));
+ fs.createReadStream(__dirname + RouteStore.public + dirname + basename + extname).pipe(fs.createWriteStream(__dirname + RouteStore.public + dirname + basename + "_l" + extname));
+ });
+ const json = zip.getEntry("doc.json");
+ let docs: any;
+ try {
+ let data = JSON.parse(json.getData().toString("utf8"));
+ docs = data.docs;
+ id = data.id;
+ docs = Object.keys(docs).map(key => docs[key]);
+ docs.forEach(mapFn);
+ await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => {
+ err && console.log(err);
+ res();
+ }, true, "newDocuments"))));
+ } catch (e) { console.log(e); }
+ fs.unlink(path_2, () => { });
+ }
+ if (id) {
+ res.send(JSON.stringify(getId(id)));
+ } else {
+ res.send(JSON.stringify("error"));
+ }
+ } catch (e) { console.log(e); }
+ });
+});
+
app.get("/whosOnline", (req, res) => {
let users: any = { active: {}, inactive: {} };
const now = Date.now();
diff --git a/src/server/remapUrl.ts b/src/server/remapUrl.ts
index 6f4d6642f..69c766d56 100644
--- a/src/server/remapUrl.ts
+++ b/src/server/remapUrl.ts
@@ -6,7 +6,8 @@ const suffixMap: { [type: string]: true } = {
"video": true,
"pdf": true,
"audio": true,
- "web": true
+ "web": true,
+ "image": true
};
async function update() {
@@ -30,10 +31,10 @@ async function update() {
const value = fields[key];
if (value && value.__type && suffixMap[value.__type]) {
const url = new URL(value.url);
- if (url.href.includes("azure")) {
+ if (url.href.includes("localhost") && url.href.includes("Bill")) {
dynfield = true;
- update.$set = { ["fields." + key + ".url"]: `${url.protocol}//localhost:1050${url.pathname}` };
+ update.$set = { ["fields." + key + ".url"]: `${url.protocol}//dash-web.eastus2.cloudapp.azure.com:1050${url.pathname}` };
}
}
}