diff options
Diffstat (limited to 'src')
39 files changed, 688 insertions, 448 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index dec6245ef..98f75d3b9 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,7 +1,7 @@ import v4 = require('uuid/v4'); import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; -import { Message, Types, Transferable } from './server/Message'; +import { Message } from './server/Message'; import { Document } from './fields/Document'; export class Utils { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4febfa7eb..b0bb74d89 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -32,6 +32,9 @@ import { action } from "mobx"; import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel"; import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel"; import { AggregateFunction } from "../northstar/model/idea/idea"; +import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; +import { IconBox } from "../views/nodes/IconBox"; +import { IconField } from "../../fields/IconFIeld"; export interface DocumentOptions { x?: number; @@ -63,6 +66,7 @@ export namespace Documents { let videoProto: Document; let audioProto: Document; let pdfProto: Document; + let iconProto: Document; const textProtoId = "textProto"; const histoProtoId = "histoProto"; const pdfProtoId = "pdfProto"; @@ -72,6 +76,7 @@ export namespace Documents { const kvpProtoId = "kvpProto"; const videoProtoId = "videoProto"; const audioProtoId = "audioProto"; + const iconProtoId = "iconProto"; export function initProtos(): Promise<void> { return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => { @@ -84,6 +89,7 @@ export namespace Documents { videoProto = fields[videoProtoId] as Document || CreateVideoPrototype(); audioProto = fields[audioProtoId] as Document || CreateAudioPrototype(); pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype(); + iconProto = fields[iconProtoId] as Document || CreateIconPrototype(); }); } function assignOptions(doc: Document, options: DocumentOptions): Document { @@ -92,6 +98,8 @@ export namespace Documents { if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); } if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); } if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); } + if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); } + if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); } if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); } if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); } if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); } @@ -139,6 +147,11 @@ export namespace Documents { histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString()); return histoProto; } + function CreateIconPrototype(): Document { + let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(), + { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data] }); + return iconProto; + } function CreateTextPrototype(): Document { let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] }); @@ -203,6 +216,9 @@ export namespace Documents { export function TextDocument(options: DocumentOptions = {}) { return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options); } + export function IconDocument(icon: string, options: DocumentOptions = {}) { + return assignToDelegate(SetInstanceOptions(iconProto, { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data], layout: IconBox.LayoutString(), ...options }, [icon, IconField]), options); + } export function PdfDocument(url: string, options: DocumentOptions = {}) { return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options); } diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss index e899cf15e..06d781263 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.scss +++ b/src/client/northstar/dash-nodes/HistogramBox.scss @@ -1,12 +1,12 @@ .histogrambox-container { padding: 0vw; position: absolute; - top: 0; - left:0; + top: -50%; + left:-50%; text-align: center; width: 100%; height: 100%; - background: black; + background: black; } .histogrambox-xaxislabel { position:absolute; diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 0e84ace50..e2ecc8c83 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -146,7 +146,7 @@ export class HistogramBox extends React.Component<FieldViewProps> { return ( <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}> {({ measureRef }) => - <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}> + <div className="histogrambox-container" ref={measureRef}> <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} > <span className="histogrambox-yaxislabel-text"> {labelY} diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4bd654e15..46658867b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -250,7 +250,7 @@ export namespace DragManager { dragData.aliasOnDrop = e.ctrlKey || e.altKey; } if (e.shiftKey) { - abortDrag(); + AbortDrag(); CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, @@ -269,20 +269,22 @@ export namespace DragManager { ); }; - const abortDrag = () => { + AbortDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - dragElements.map(dragElement => dragDiv.removeChild(dragElement)); + dragElements.map(dragElement => { if (dragElement.parentNode == dragDiv) dragDiv.removeChild(dragElement); }); eles.map(ele => (ele.hidden = false)); }; const upHandler = (e: PointerEvent) => { - abortDrag(); + AbortDrag(); FinishDrag(eles, e, dragData, options, finishDrag); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } + export let AbortDrag: () => void = emptyFunction; + function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { let removed = dragEles.map(dragEle => { let parent = dragEle.parentElement; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 320553952..92d78696e 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -3,6 +3,7 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { Document } from "../../fields/Document"; import { Main } from "../views/Main"; import { MainOverlayTextBox } from "../views/MainOverlayTextBox"; +import { DragManager } from "./DragManager"; export namespace SelectionManager { class Manager { @@ -18,13 +19,13 @@ export namespace SelectionManager { if (manager.SelectedDocuments.indexOf(doc) === -1) { manager.SelectedDocuments.push(doc); - doc.props.onActiveChanged(true); + doc.props.whenActiveChanged(true); } } @action DeselectAll(): void { - manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false)); + manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; MainOverlayTextBox.Instance.SetTextDoc(); } @@ -36,7 +37,7 @@ export namespace SelectionManager { } @action ReselectAll2(sdocs: DocumentView[]) { - sdocs.map(s => SelectionManager.SelectDoc(s, false)); + sdocs.map(s => SelectionManager.SelectDoc(s, true)); } } diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 5c2d66480..70d9ad772 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -35,6 +35,10 @@ cursor: pointer; position: relative; padding-right: 15px; + margin: 3px; + background: #333333; + border-radius: 3px; + text-align: center; } .ProseMirror-menu-dropdown-wrap { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index a92cbd263..4f0eb7d63 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -34,6 +34,9 @@ export class TooltipTextMenu { private fontSizeToNum: Map<MarkType, number>; private fontStylesToName: Map<MarkType, string>; private fontSizeIndicator: HTMLSpanElement = document.createElement("span"); + //dropdown doms + private fontSizeDom: Node; + private fontStyleDom: Node; constructor(view: EditorView, editorProps: FieldViewProps) { this.view = view; @@ -76,7 +79,7 @@ export class TooltipTextMenu { this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); this.fontStylesToName.set(schema.marks.arial, "Arial"); this.fontStylesToName.set(schema.marks.georgia, "Georgia"); - this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans"); + this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS"); this.fontStylesToName.set(schema.marks.tahoma, "Tahoma"); this.fontStylesToName.set(schema.marks.impact, "Impact"); this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); @@ -93,23 +96,15 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p72, 72); this.fontSizes = Array.from(this.fontSizeToNum.keys()); - this.addFontDropdowns(); + //this.addFontDropdowns(); this.update(view, undefined); } - //adds font size and font style dropdowns - addFontDropdowns() { + //label of dropdown will change to given label + updateFontSizeDropdown(label: string) { //filtering function - might be unecessary let cut = (arr: MenuItem[]) => arr.filter(x => x); - //font STYLES - let fontBtns: MenuItem[] = []; - this.fontStylesToName.forEach((name, mark) => { - fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 120px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); - }); - - //font size indicator - this.fontSizeIndicator = this.icon("12", "font-size-indicator"); //font SIZES let fontSizeBtns: MenuItem[] = []; @@ -117,14 +112,32 @@ export class TooltipTextMenu { fontSizeBtns.push(this.dropdownBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); }); - //dropdown to hold font btns - let dd_fontStyle = new Dropdown(cut(fontBtns), { label: "Font Style", css: "color:white;" }) as MenuItem; - let dd_fontSize = new Dropdown(cut(fontSizeBtns), { label: "Font Size", css: "color:white; margin-left: -6px;" }) as MenuItem; - this.tooltip.appendChild(dd_fontStyle.render(this.view).dom); - this.tooltip.appendChild(this.fontSizeIndicator); - this.tooltip.appendChild(dd_fontSize.render(this.view).dom); - dd_fontStyle.render(this.view).dom.nodeValue = "TEST"; - console.log(dd_fontStyle.render(this.view).dom.nodeValue); + if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); } + this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), { + label: label, + css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;" + }) as MenuItem).render(this.view).dom; + this.tooltip.appendChild(this.fontSizeDom); + } + + //label of dropdown will change to given label + updateFontStyleDropdown(label: string) { + //filtering function - might be unecessary + let cut = (arr: MenuItem[]) => arr.filter(x => x); + + //font STYLES + let fontBtns: MenuItem[] = []; + this.fontStylesToName.forEach((name, mark) => { + fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + }); + + if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); } + this.fontStyleDom = (new Dropdown(cut(fontBtns), { + label: label, + css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;" + }) as MenuItem).render(this.view).dom; + + this.tooltip.appendChild(this.fontStyleDom); } //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text @@ -246,30 +259,32 @@ export class TooltipTextMenu { let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale; let mid = Math.min(start.left, end.left) + width; - this.tooltip.style.width = 220 + "px"; + this.tooltip.style.width = 225 + "px"; this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px"; + //UPDATE FONT STYLE DROPDOWN let activeStyles = this.activeMarksOnSelection(this.fontStyles); if (activeStyles.length === 1) { // if we want to update something somewhere with active font name let fontName = this.fontStylesToName.get(activeStyles[0]); + if (fontName) { this.updateFontStyleDropdown(fontName); } } else if (activeStyles.length === 0) { //crimson on default + this.updateFontStyleDropdown("Crimson Text"); + } else { + this.updateFontStyleDropdown("Various"); } - //update font size indicator + //UPDATE FONT SIZE DROPDOWN let activeSizes = this.activeMarksOnSelection(this.fontSizes); if (activeSizes.length === 1) { //if there's only one active font size let size = this.fontSizeToNum.get(activeSizes[0]); - if (size) { - this.fontSizeIndicator.innerHTML = String(size); - } - //should be 14 on default + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } } else if (activeSizes.length === 0) { - this.fontSizeIndicator.innerHTML = "14"; - //multiple font sizes selected - } else { - this.fontSizeIndicator.innerHTML = ""; + //should be 14 on default + this.updateFontSizeDropdown("14 pt"); + } else { //multiple font sizes selected + this.updateFontSizeDropdown("Various"); } } diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index c1a949639..f78bf9ff8 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -3,14 +3,14 @@ .documentDecorations { position: absolute; } -#documentDecorations-container { +.documentDecorations-container { + z-index: $docDecorations-zindex; position: absolute; top: 0; left:0; display: grid; - z-index: $docDecorations-zindex; grid-template-rows: 20px 8px 1fr 8px; - grid-template-columns: 8px 8px 1fr 8px 8px; + grid-template-columns: 8px 16px 1fr 8px 8px; pointer-events: none; #documentDecorations-centerCont { @@ -88,6 +88,11 @@ pointer-events: all; text-align: center; cursor: pointer; + position: absolute; + left: 0px; + top: 0px; + width: $MINIMIZED_ICON_SIZE; + height: $MINIMIZED_ICON_SIZE; } .documentDecorations-background { background: lightblue; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 16fac0694..1f5078c85 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -5,6 +5,7 @@ import { KeyStore } from "../../fields/KeyStore"; import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; import { TextField } from "../../fields/TextField"; +import { Document } from "../../fields/Document"; import { emptyFunction } from "../../Utils"; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; @@ -14,6 +15,8 @@ import { MainOverlayTextBox } from "./MainOverlayTextBox"; import { DocumentView } from "./nodes/DocumentView"; import { LinkMenu } from "./nodes/LinkMenu"; import React = require("react"); +import { CompileScript } from "../util/Scripting"; +import { IconBox } from "./nodes/IconBox"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -36,6 +39,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @observable private _hidden = false; @observable private _opacity = 1; @observable private _dragging = false; + @observable private _iconifying = false; constructor(props: Readonly<{}>) { @@ -175,25 +179,65 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onCloseUp); } } + _downX = 0; + _downY = 0; onMinimizeDown = (e: React.PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { + this._downX = e.pageX; + this._downY = e.pageY; document.removeEventListener("pointermove", this.onMinimizeMove); document.addEventListener("pointermove", this.onMinimizeMove); document.removeEventListener("pointerup", this.onMinimizeUp); document.addEventListener("pointerup", this.onMinimizeUp); } } + + @observable _minimizedX = 0; + @observable _minimizedY = 0; + @action onMinimizeMove = (e: PointerEvent): void => { e.stopPropagation(); + let dx = e.pageX - this._downX; + let dy = e.pageY - this._downY; + if (Math.abs(dx) > 4 || Math.abs(dy) > 4) { + this._iconifying = true; + let xf = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().scale(SelectionManager.SelectedDocuments()[0].props.ContentScaling()).inverse().transformPoint(0, 0); + let dx = e.pageX - xf[0]; + let dy = e.pageY - xf[1]; + this._minimizedX = e.clientX; + this._minimizedY = e.clientY; + if (Math.abs(dx) < 20 && Math.abs(dy) < 20) { + this._minimizedX = xf[0]; + this._minimizedY = xf[1]; + } + SelectionManager.SelectedDocuments().map(dv => { + let minDoc = dv.props.Document.Get(KeyStore.MinimizedDoc); + if (minDoc instanceof Document) { + let where = (dv.props.ScreenToLocalTransform()).scale(dv.props.ContentScaling()).transformPoint(this._minimizedX, this._minimizedY); + let minDocument = minDoc as Document; + minDocument.SetNumber(KeyStore.X, where[0] + dv.props.Document.GetNumber(KeyStore.X, 0)); + minDocument.SetNumber(KeyStore.Y, where[1] + dv.props.Document.GetNumber(KeyStore.Y, 0)); + } + }); + } } + @action onMinimizeUp = (e: PointerEvent): void => { e.stopPropagation(); if (e.button === 0) { - SelectionManager.SelectedDocuments().map(dv => dv.minimize()); + let dx = e.clientX - this._downX; + let dy = e.clientY - this._downY; + if (Math.abs(dx) < 4 && Math.abs(dy) < 4 && !this._iconifying) { + SelectionManager.SelectedDocuments().map(dv => dv.minimize()); + SelectionManager.DeselectAll(); + } else { + this._minimizedX = this._minimizedY = 0; + } document.removeEventListener("pointermove", this.onMinimizeMove); document.removeEventListener("pointerup", this.onMinimizeUp); } + this._iconifying = false; } onPointerDown = (e: React.PointerEvent): void => { @@ -370,11 +414,22 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> // } render() { var bounds = this.Bounds; - if (bounds.x === Number.MAX_VALUE) { + let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; + if (bounds.x === Number.MAX_VALUE || !seldoc) { return (null); } - // console.log(this._documents.length) - // let test = this._documents[0].props.Document.Title; + let selpos = this._minimizedX !== 0 || this._minimizedY !== 0 ? + [this._minimizedX - 12 + (!this._iconifying ? 8 : 0), this._minimizedY - 12 + (!this._iconifying ? 28 : 0)] : + [0, this._iconifying ? -18 : 0]; + let minimizeIcon = ( + <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown} + style={{ transform: `translate(${selpos[0]}px,${selpos[1]}px)`, }}> + {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."} + </div>); + if (this._iconifying) { + return (<div className="documentDecorations-container" > {minimizeIcon} </div>); + } + if (this.Hidden) { return (null); } @@ -406,14 +461,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} > </div> - <div id="documentDecorations-container" style={{ + <div className="documentDecorations-container" style={{ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px", left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, opacity: this._opacity }}> - <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div> + {minimizeIcon} + <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} /> <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div> <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 0469211fa..09ef30f6b 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -27,7 +27,7 @@ import '../northstar/model/ModelExtensions'; import { HistogramOperation } from '../northstar/operations/HistogramOperation'; import '../northstar/utils/Extensions'; import { Server } from '../Server'; -import { SetupDrag } from '../util/DragManager'; +import { SetupDrag, DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; @@ -38,6 +38,7 @@ import "./Main.scss"; import { MainOverlayTextBox } from './MainOverlayTextBox'; import { DocumentView } from './nodes/DocumentView'; import { PreviewCursor } from './PreviewCursor'; +import { SelectionManager } from '../util/SelectionManager'; @observer @@ -111,6 +112,12 @@ export class Main extends React.Component { // window.addEventListener("pointermove", (e) => this.reportLocation(e)) window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler + window.addEventListener("keydown", (e) => { + if (e.key == "Escape") { + DragManager.AbortDrag(); + SelectionManager.DeselectAll() + } + }, false); // drag event handler // click interactions for the context menu document.addEventListener("pointerdown", action(function (e: PointerEvent) { if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) { @@ -190,7 +197,7 @@ export class Main extends React.Component { selectOnLoad={false} focus={emptyDocFunction} parentActive={returnTrue} - onActiveChanged={emptyFunction} + whenActiveChanged={emptyFunction} ContainingCollectionView={undefined} />} </div> } @@ -210,9 +217,9 @@ export class Main extends React.Component { let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" })); let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" })); let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true })); - let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" })); + let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" })); let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" })); - let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" })); + let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })); diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 141b3ad74..8cb01117c 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -101,7 +101,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> return <div className="mainOverlayTextBox-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} > <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}> <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} - selectOnLoad={true} ContainingCollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} /> + selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} /> </div> </ div>; } diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 4fda38a26..4755b2d57 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -22,7 +22,7 @@ export interface CollectionRenderProps { removeDocument: (document: Document) => boolean; moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; active: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; } export interface CollectionViewProps extends FieldViewProps { @@ -55,9 +55,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { //TODO should this be observable? private _isChildActive = false; - onActiveChanged = (isActive: boolean) => { + whenActiveChanged = (isActive: boolean) => { this._isChildActive = isActive; - this.props.onActiveChanged(isActive); + this.props.whenActiveChanged(isActive); } createsCycle(documentToAdd: Document, containerDocument: Document): boolean { @@ -90,7 +90,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { let props = this.props; var curPage = props.Document.GetNumber(KeyStore.CurPage, -1); doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage)); - if (this.isAnnotationOverlay) { + if (true || this.isAnnotationOverlay) { doc.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1)); } if (curPage >= 0) { @@ -184,7 +184,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { removeDocument: this.removeDocument, moveDocument: this.moveDocument, active: this.active, - onActiveChanged: this.onActiveChanged, + whenActiveChanged: this.whenActiveChanged, }; const viewtype = this.collectionViewType; return ( diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 50da2b11d..0e7e0afa7 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -2,15 +2,6 @@ .collectiondockingview-content { height: 100%; - text-align:center; - .documentView-node-topmost { - text-align:left; - transform-origin: center top; - display: inline-block; - } -} -.collectiondockingview-content-height { - height: 100%; } .lm_active .messageCounter{ color:white; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 2b886adb6..e4c647635 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -19,7 +19,7 @@ import { ServerUtils } from "../../../server/ServerUtil"; import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; import { TextField } from "../../../fields/TextField"; import { ListField } from "../../../fields/ListField"; -import { thisExpression } from "babel-types"; +import { Transform } from '../../util/Transform' @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -316,7 +316,7 @@ interface DockedFrameProps { @observer export class DockedFrameRenderer extends React.Component<DockedFrameProps> { - private _mainCont = React.createRef<HTMLDivElement>(); + _mainCont = React.createRef<HTMLDivElement>(); @observable private _panelWidth = 0; @observable private _panelHeight = 0; @observable private _document: Opt<Document>; @@ -326,50 +326,49 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document)); } - private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); - private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); - private _contentScaling = () => { - let wscale = this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); - if (wscale * this._nativeHeight() > this._panelHeight) - return this._panelHeight / (this._nativeHeight() ? this._nativeHeight() : this._panelHeight); + nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth); + nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight); + contentScaling = () => { + let wscale = this._panelWidth / (this.nativeWidth() ? this.nativeWidth() : this._panelWidth); + if (wscale * this.nativeHeight() > this._panelHeight) + return this._panelHeight / (this.nativeHeight() ? this.nativeHeight() : this._panelHeight); return wscale; } ScreenToLocalTransform = () => { - let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement); - let scaling = scale; - { - let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!); - scaling = scale; + if (this._mainCont.current && this._mainCont.current.children) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement); + scale = Utils.GetScreenTransform(this._mainCont.current!).scale; + return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling()); } - return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scaling / this._contentScaling()); + return Transform.Identity(); } + get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; } - render() { - if (!this._document) { - return (null); - } - let wscale = this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth); - let name = (wscale * this._nativeHeight() > this._panelHeight) ? "" : "-height"; - var content = - <div className={`collectionDockingView-content${name}`} ref={this._mainCont}> - <DocumentView key={this._document.Id} Document={this._document} + get content() { + return ( + <div className="collectionDockingView-content" ref={this._mainCont} + style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> + <DocumentView key={this._document!.Id} Document={this._document!} addDocument={undefined} removeDocument={undefined} - ContentScaling={this._contentScaling} - PanelWidth={this._nativeWidth} - PanelHeight={this._nativeHeight} + ContentScaling={this.contentScaling} + PanelWidth={this.nativeWidth} + PanelHeight={this.nativeHeight} ScreenToLocalTransform={this.ScreenToLocalTransform} isTopMost={true} selectOnLoad={false} parentActive={returnTrue} - onActiveChanged={emptyFunction} + whenActiveChanged={emptyFunction} focus={emptyDocFunction} ContainingCollectionView={undefined} /> - </div>; + </div>); + } - return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}> - {({ measureRef }) => <div ref={measureRef}> {content} </div>} - </Measure>; + render() { + return !this._document ? (null) : + <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}> + {({ measureRef }) => <div ref={measureRef}> {this.content} </div>} + </Measure>; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index c8bfedff4..cfdb3ab22 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -1,61 +1,6 @@ @import "../globalCssVariables"; -//options menu styling -#schemaOptionsMenuBtn { - position: absolute; - height: 20px; - width: 20px; - border-radius: 50%; - z-index: 21; - right: 4px; - top: 4px; - pointer-events: auto; - background-color:black; - display:inline-block; - padding: 0px; - font-size: 100%; -} - -ul { - list-style-type: disc; -} - -#schema-options-header { - text-align: center; - padding: 0px; - margin: 0px; -} -.schema-options-subHeader { - color: $intermediate-color; - margin-bottom: 5px; -} -#schemaOptionsMenuBtn:hover { - transform: scale(1.15); -} - -#preview-schema-checkbox-div { - margin-left: 20px; - font-size: 12px; -} - #options-flyout-div { - text-align: left; - padding:0px; - z-index: 100; - font-family: $sans-serif; - padding-left: 5px; - } - - #schema-col-checklist { - overflow: scroll; - text-align: left; - //background-color: $light-color-secondary; - line-height: 25px; - max-height: 175px; - font-family: $sans-serif; - font-size: 12px; - } - .collectionSchemaView-container { border-width: $COLLECTION_BORDER_WIDTH; @@ -66,18 +11,31 @@ ul { position: absolute; width: 100%; height: 100%; - - .collectionSchemaView-content { - position: absolute; - height: 100%; - width: 100%; - overflow: auto; + + .collectionSchemaView-cellContents { + height: $MAX_ROW_HEIGHT; } + .collectionSchemaView-previewRegion { position: relative; background: $light-color; float: left; height: 100%; + .collectionSchemaView-previewDoc { + height: 100%; + width: 100%; + position: absolute; + } + .collectionSchemaView-input { + position: absolute; + max-width: 150px; + width: 100%; + bottom: 0px; + } + .documentView-node:first-child { + position: relative; + background: $light-color; + } } .collectionSchemaView-previewHandle { position: absolute; @@ -151,7 +109,7 @@ ul { } .rt-tr-group { direction: ltr; - max-height: 44px; + max-height: $MAX_ROW_HEIGHT; } .rt-td { border-width: 1px; @@ -183,7 +141,7 @@ ul { } .ReactTable .rt-th, .ReactTable .rt-td { - max-height: 44; + max-height: $MAX_ROW_HEIGHT; padding: 3px 7px; font-size: 13px; text-align: center; @@ -193,13 +151,71 @@ ul { border-bottom-style: solid; border-bottom-width: 1; } + .documentView-node-topmost { + text-align:left; + transform-origin: center top; + display: inline-block; + } .documentView-node:first-child { background: $light-color; - .imageBox-cont img { - object-fit: contain; - } } } +//options menu styling +#schemaOptionsMenuBtn { + position: absolute; + height: 20px; + width: 20px; + border-radius: 50%; + z-index: 21; + right: 4px; + top: 4px; + pointer-events: auto; + background-color:black; + display:inline-block; + padding: 0px; + font-size: 100%; +} + +ul { + list-style-type: disc; +} + +#schema-options-header { + text-align: center; + padding: 0px; + margin: 0px; +} +.schema-options-subHeader { + color: $intermediate-color; + margin-bottom: 5px; +} +#schemaOptionsMenuBtn:hover { + transform: scale(1.15); +} + +#preview-schema-checkbox-div { + margin-left: 20px; + font-size: 12px; +} + + #options-flyout-div { + text-align: left; + padding:0px; + z-index: 100; + font-family: $sans-serif; + padding-left: 5px; + } + + #schema-col-checklist { + overflow: scroll; + text-align: left; + //background-color: $light-color-secondary; + line-height: 25px; + max-height: 175px; + font-family: $sans-serif; + font-size: 12px; + } + .Resizer { box-sizing: border-box; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 1defdba7e..90077b053 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -4,15 +4,15 @@ import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; -import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; +import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss' import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; import { Field, Opt } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { emptyDocFunction, emptyFunction, returnFalse, returnOne } from "../../../Utils"; +import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils"; import { Server } from "../../Server"; import { SetupDrag } from "../../util/DragManager"; import { CompileScript, ToField } from "../../util/Scripting"; @@ -34,42 +34,35 @@ import { CollectionSubView } from "./CollectionSubView"; class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> { @observable key: Key | undefined; - componentWillReceiveProps() { - Server.GetField(this.props.keyId, action((field: Opt<Field>) => { - if (field instanceof Key) { - this.key = field; - } - })); + constructor(props: any) { + super(props); + Server.GetField(this.props.keyId, action((field: Opt<Field>) => field instanceof Key && (this.key = field))); } render() { - if (this.key) { - return (<div key={this.key.Id}> + return !this.key ? (null) : + (<div key={this.key.Id}> <input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} /> {this.key.Name} </div>); - } - return (null); } } @observer export class CollectionSchemaView extends CollectionSubView { - private _mainCont = React.createRef<HTMLDivElement>(); + private _mainCont?: HTMLDivElement; private _startSplitPercent = 0; private DIVIDER_WIDTH = 4; @observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author]; - @observable _contentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ContentScaling prop of the DocumentView - @observable _dividerX = 0; - @observable _panelWidth = 0; - @observable _panelHeight = 0; @observable _selectedIndex = 0; @observable _columnsPercentage = 0; @observable _keys: Key[] = []; + @observable _newKeyName: string = ""; @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); } - + @computed get columns() { return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); } + @computed get borderWidth() { return COLLECTION_BORDER_WIDTH; } renderCell = (rowProps: CellInfo) => { let props: FieldViewProps = { @@ -83,7 +76,7 @@ export class CollectionSchemaView extends CollectionSubView { ScreenToLocalTransform: Transform.Identity, focus: emptyDocFunction, active: returnFalse, - onActiveChanged: emptyFunction, + whenActiveChanged: emptyFunction, }; let contents = ( <FieldView {...props} /> @@ -107,11 +100,11 @@ export class CollectionSchemaView extends CollectionSubView { return false; }; return ( - <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}> + <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document.Id} ref={reference}> <EditableView display={"inline"} contents={contents} - height={56} + height={Number(MAX_ROW_HEIGHT)} GetValue={() => { let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { @@ -165,9 +158,9 @@ export class CollectionSchemaView extends CollectionSubView { }; } - @computed - get columns() { - return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); + private createTarget = (ele: HTMLDivElement) => { + this._mainCont = ele; + super.CreateDropTarget(ele); } @action @@ -187,31 +180,12 @@ export class CollectionSchemaView extends CollectionSubView { //toggles preview side-panel of schema @action toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => { - this._startSplitPercent = this.splitPercentage; - if (this._startSplitPercent === this.splitPercentage) { - this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); - } - } - - @computed - get findAllDocumentKeys(): { [id: string]: boolean } { - const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); - let keys: { [id: string]: boolean } = {}; - if (this._optionsActivated > -1) { - // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. - // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be - // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. - // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu - // is displayed (unlikely) it won't show up until something else changes. - untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false)))); - } - this.columns.forEach(key => keys[key.Id] = true); - return keys; + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0); } @action onDividerMove = (e: PointerEvent): void => { - let nativeWidth = this._mainCont.current!.getBoundingClientRect(); + let nativeWidth = this._mainCont!.getBoundingClientRect(); this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100))); } @action @@ -230,28 +204,6 @@ export class CollectionSchemaView extends CollectionSubView { document.addEventListener('pointerup', this.onDividerUp); } - @observable _tableWidth = 0; - @action - setTableDimensions = (r: any) => { - this._tableWidth = r.entry.width; - } - @action - setScaling = (r: any) => { - const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); - const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; - this._panelWidth = r.entry.width; - this._panelHeight = r.entry.height ? r.entry.height : this._panelHeight; - this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width); - } - - @computed - get borderWidth() { return COLLECTION_BORDER_WIDTH; } - getContentScaling = (): number => this._contentScaling; - getPanelWidth = (): number => this._panelWidth; - getPanelHeight = (): number => this._panelHeight; - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling); - getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling); - onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) { if (this.props.isSelected()) @@ -260,127 +212,141 @@ export class CollectionSchemaView extends CollectionSubView { } } - @action - addColumn = () => { - this.columns.push(new Key(this.newKeyName)); - this.newKeyName = ""; - } - - @observable - newKeyName: string = ""; - - @action - newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => { - this.newKeyName = e.currentTarget.value; - } onWheel = (e: React.WheelEvent): void => { if (this.props.active()) { e.stopPropagation(); } } - @observable _optionsActivated: number = 0; @action - OptionsMenuDown = (e: React.PointerEvent) => { - this._optionsActivated++; + addColumn = () => { + this.columns.push(new Key(this._newKeyName)); + this._newKeyName = ""; } - @observable previewScript: string = "this"; + @action + newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._newKeyName = e.currentTarget.value; + } + + @observable previewScript: string = ""; @action onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.previewScript = e.currentTarget.value; } - render() { - library.add(faCog); - library.add(faPlus); - const columns = this.columns; + get previewDocument(): Document | undefined { const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined; - //all the keys/columns that will be displayed in the schema - const allKeys = this.findAllDocumentKeys; - let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined; + return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : selected) : undefined; + } + get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); } + get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; } + get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; } + + private previewDocNativeWidth = () => this.previewDocument!.GetNumber(KeyStore.NativeWidth, this.previewRegionWidth); + private previewDocNativeHeight = () => this.previewDocument!.GetNumber(KeyStore.NativeHeight, this.previewRegionHeight); + private previewContentScaling = () => { + let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth); + if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) + return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight); + return wscale; + } + private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling(); + private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling(); + get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; } + getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate( + - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset, + - this.borderWidth).scale(1 / this.previewContentScaling()); + @computed + get previewPanel() { // let doc = CompileScript(this.previewScript, { this: selected }, true)(); - let content = this._selectedIndex === -1 || !selected ? (null) : ( - <Measure onResize={this.setScaling}> - {({ measureRef }) => - <div className="collectionSchemaView-content" ref={measureRef}> - {doc instanceof Document ? - <DocumentView Document={doc} - addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} - isTopMost={false} - selectOnLoad={false} - ScreenToLocalTransform={this.getPreviewTransform} - ContentScaling={this.getContentScaling} - PanelWidth={this.getPanelWidth} - PanelHeight={this.getPanelHeight} - ContainingCollectionView={this.props.CollectionView} - focus={emptyDocFunction} - parentActive={this.props.active} - onActiveChanged={this.props.onActiveChanged} /> : null} - <input value={this.previewScript} onChange={this.onPreviewScriptChange} - style={{ position: 'absolute', bottom: '0px' }} /> - </div> - } - </Measure> - ); - let dividerDragger = this.splitPercentage === 0 ? (null) : - <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />; - - //options button and menu - let optionsMenu = !this.props.active() ? (null) : (<Flyout - anchorPoint={anchorPoints.LEFT_TOP} - content={<div> - <div id="schema-options-header"><h5><b>Options</b></h5></div> - <div id="options-flyout-div"> - <h6 className="schema-options-subHeader">Preview Window</h6> - <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div> - <h6 className="schema-options-subHeader" >Displayed Columns</h6> - <ul id="schema-col-checklist" > - {Array.from(Object.keys(allKeys)).map(item => - (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))} - </ul> - <input value={this.newKeyName} onChange={this.newKeyChange} /> - <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button> + return !this.previewDocument ? (null) : ( + <div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}> + <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> + <DocumentView Document={this.previewDocument} isTopMost={false} selectOnLoad={false} + addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} + ScreenToLocalTransform={this.getPreviewTransform} + ContentScaling={this.previewContentScaling} + PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight} + ContainingCollectionView={this.props.CollectionView} + focus={emptyDocFunction} + parentActive={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + /> </div> + <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange} + style={{ left: `calc(50% - ${Math.min(75, this.previewPanelWidth() / 2)}px)` }} /> </div> - }> - <button id="schemaOptionsMenuBtn" onPointerDown={this.OptionsMenuDown}><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button> - </Flyout>); + ); + } - return ( - <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}> - <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> - <Measure onResize={this.setTableDimensions}> - {({ measureRef }) => - <div className="collectionSchemaView-tableContainer" ref={measureRef} style={{ width: `calc(100% - ${this.splitPercentage}%)` }}> - <ReactTable - data={children} - pageSize={children.length} - page={0} - showPagination={false} - columns={columns.map(col => ({ - Header: col.Name, - accessor: (doc: Document) => [doc, col], - id: col.Id - }))} - column={{ - ...ReactTableDefaults.column, - Cell: this.renderCell, + get documentKeysCheckList() { + const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); + let keys: { [id: string]: boolean } = {}; + // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. + // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be + // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked. + // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu + // is displayed (unlikely) it won't show up until something else changes. + untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false)))); - }} - getTrProps={this.getTrProps} - /> - </div>} - </Measure> - {dividerDragger} - <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}> - {content} + this.columns.forEach(key => keys[key.Id] = true); + return Array.from(Object.keys(keys)).map(item => + (<KeyToggle checked={keys[item]} key={item} keyId={item} toggle={this.toggleKey} />)); + } + + get tableOptionsPanel() { + return !this.props.active() ? (null) : + (<Flyout + anchorPoint={anchorPoints.LEFT_TOP} + content={<div> + <div id="schema-options-header"><h5><b>Options</b></h5></div> + <div id="options-flyout-div"> + <h6 className="schema-options-subHeader">Preview Window</h6> + <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div> + <h6 className="schema-options-subHeader" >Displayed Columns</h6> + <ul id="schema-col-checklist" > + {this.documentKeysCheckList} + </ul> + <input value={this._newKeyName} onChange={this.newKeyChange} /> + <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button> </div> - {optionsMenu} </div> - </div > + }> + <button id="schemaOptionsMenuBtn" ><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button> + </Flyout>); + } + + @computed + get dividerDragger() { + return this.splitPercentage === 0 ? (null) : + <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />; + } + + render() { + library.add(faCog); + library.add(faPlus); + const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]); + return ( + <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} + onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}> + <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}> + <ReactTable data={children} page={0} pageSize={children.length} showPagination={false} + columns={this.columns.map(col => ({ + Header: col.Name, + accessor: (doc: Document) => [doc, col], + id: col.Id + }))} + column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }} + getTrProps={this.getTrProps} + /> + </div> + {this.dividerDragger} + {this.previewPanel} + {this.tableOptionsPanel} + </div> ); } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d3d69b1af..5c3b2e586 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -15,7 +15,6 @@ import { ServerUtils } from "../../../server/ServerUtil"; import { Server } from "../../Server"; import { FieldViewProps } from "../nodes/FieldView"; import * as rp from 'request-promise'; -import { emptyFunction } from "../../../Utils"; import { CollectionView } from "./CollectionView"; import { CollectionPDFView } from "./CollectionPDFView"; import { CollectionVideoView } from "./CollectionVideoView"; @@ -24,6 +23,8 @@ export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Document, allowDuplicates?: boolean) => boolean; removeDocument: (document: Document) => boolean; moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; + PanelWidth: () => number; + PanelHeight: () => number; } export interface SubCollectionViewProps extends CollectionViewProps { @@ -42,6 +43,9 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> { this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); } } + protected CreateDropTarget(ele: HTMLDivElement) { + this.createDropTarget(ele); + } @action protected setCursorPosition(position: [number, number]) { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 51a02fc25..e0387f4b4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,20 +1,17 @@ import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable, trace } from "mobx"; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { SetupDrag, DragManager } from "../../util/DragManager"; +import { DragManager, SetupDrag } from "../../util/DragManager"; import { EditableView } from "../EditableView"; -import "./CollectionTreeView.scss"; -import { CollectionView } from "./CollectionView"; -import * as globalCssVariables from "../../views/globalCssVariables.scss"; import { CollectionSubView } from "./CollectionSubView"; +import "./CollectionTreeView.scss"; import React = require("react"); -import { props } from 'bluebird'; export interface TreeViewProps { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 8868f7df0..20c5a84bf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -23,10 +23,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo let l = this.props.LinkDocs; let a = this.props.A; let b = this.props.B; - let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Width() / 2); - let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Height() / 2); - let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Width() / 2); - let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Height() / 2); + let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Width() / 2); + let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Height() / 2); + let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Width() / 2); + let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Height() / 2); return ( <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown} style={{ strokeWidth: `${l.length * 5}` }} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 392bd514f..57706b51e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,11 +1,4 @@ @import "../../globalCssVariables"; -.collectionfreeformview-measure { - position: inherit; - top: 0; - left: 0; - width: 100%; - height: 100%; - } .collectionfreeformview { position: inherit; top: 0; @@ -52,7 +45,7 @@ } opacity: 0.99; - border-width: 0; + border-width: 0; border-color: transparent; border-style: solid; border-radius: $border-radius; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 97708ce19..945c01059 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,5 @@ import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; -import Measure from "react-measure"; import { Document } from "../../../../fields/Document"; import { KeyStore } from "../../../../fields/KeyStore"; import { emptyFunction, returnFalse, returnOne } from "../../../../Utils"; @@ -22,14 +21,15 @@ import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); +import { BooleanField } from "../../../../fields/BooleanField"; @observer export class CollectionFreeFormView extends CollectionSubView { private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type) private _lastX: number = 0; private _lastY: number = 0; - @observable private _pwidth: number = 0; - @observable private _pheight: number = 0; + private get _pwidth() { return this.props.PanelWidth(); } + private get _pheight() { return this.props.PanelHeight(); } @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } @@ -49,9 +49,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.addDocument(newBox, false); } private addDocument = (newBox: Document, allowDuplicates: boolean) => { - if (this.isAnnotationOverlay) { - newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1)); - } + newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1)); return this.props.addDocument(this.bringToFront(newBox), false); } private selectDocuments = (docs: Document[]) => { @@ -76,16 +74,21 @@ export class CollectionFreeFormView extends CollectionSubView { if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) { const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset); if (de.data.droppedDocuments.length) { - let dropX = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0); - let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0); + let dragDoc = de.data.droppedDocuments[0]; + let dropX = dragDoc.GetNumber(KeyStore.X, 0); + let dropY = dragDoc.GetNumber(KeyStore.Y, 0); de.data.droppedDocuments.map(d => { - d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - dropX)); - d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - dropY)); - if (!d.GetNumber(KeyStore.Width, 0)) { - d.SetNumber(KeyStore.Width, 300); - } - if (!d.GetNumber(KeyStore.Height, 0)) { - d.SetNumber(KeyStore.Height, 300); + d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0)) - dropX); + d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0)) - dropY); + if (!d.GetBoolean(KeyStore.IsMinimized, false)) { + if (!d.GetNumber(KeyStore.Width, 0)) { + d.SetNumber(KeyStore.Width, 300); + } + if (!d.GetNumber(KeyStore.Height, 0)) { + let nw = d.GetNumber(KeyStore.NativeWidth, 0); + let nh = d.GetNumber(KeyStore.NativeHeight, 0); + d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300); + } } this.bringToFront(d); }); @@ -185,14 +188,16 @@ export class CollectionFreeFormView extends CollectionSubView { // if (modes[e.deltaMode] === 'pixels') coefficient = 50; // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height?? let deltaScale = (1 - (e.deltaY / coefficient)); + if (deltaScale < 0) deltaScale = -deltaScale; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); } let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY); let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); - this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale); - this.setPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale); + let safeScale = Math.abs(localTransform.Scale); + this.props.Document.SetNumber(KeyStore.Scale, Math.abs(safeScale)); + this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); e.stopPropagation(); } } @@ -248,7 +253,7 @@ export class CollectionFreeFormView extends CollectionSubView { ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, parentActive: this.props.active, - onActiveChanged: this.props.active, + whenActiveChanged: this.props.active, }; } @@ -258,7 +263,9 @@ export class CollectionFreeFormView extends CollectionSubView { let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => { var page = doc.GetNumber(KeyStore.Page, -1); if (page === curPage || page === -1) { - prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />); + let minim = doc.GetT(KeyStore.IsMinimized, BooleanField); + if (minim === undefined || (minim && !minim.Data)) + prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />); } return prev; }, [] as JSX.Element[]); @@ -269,11 +276,6 @@ export class CollectionFreeFormView extends CollectionSubView { } @action - onResize = (r: any) => { - this._pwidth = r.entry.width; - this._pheight = r.entry.height; - } - @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } @@ -281,29 +283,24 @@ export class CollectionFreeFormView extends CollectionSubView { render() { const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`; return ( - <Measure onResize={this.onResize}> - {({ measureRef }) => ( - <div className="collectionfreeformview-measure" ref={measureRef}> - <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel} - onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} > - <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} - addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} - getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> - <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} - zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> - <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} /> - <CollectionFreeFormLinksView {...this.props} key="freeformLinks"> - <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > - {this.childViews} - </InkingCanvas> - </CollectionFreeFormLinksView> - <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> - </CollectionFreeFormViewPannableContents> - <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} /> - </MarqueeView> - </div> - </div>)} - </Measure> + <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel} + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} > + <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} + addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} + getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> + <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} + zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> + <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} /> + <CollectionFreeFormLinksView {...this.props} key="freeformLinks"> + <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > + {this.childViews} + </InkingCanvas> + </CollectionFreeFormLinksView> + <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> + </CollectionFreeFormViewPannableContents> + <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} /> + </MarqueeView> + </div> ); } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 65f461b27..bf918beba 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -11,6 +11,7 @@ import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; +import { MINIMIZED_ICON_SIZE } from '../../../views/globalCssVariables.scss' import "./MarqueeView.scss"; import React = require("react"); diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss index 5c8e9c8fc..4f68b71b0 100644 --- a/src/client/views/globalCssVariables.scss +++ b/src/client/views/globalCssVariables.scss @@ -23,7 +23,11 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu $docDecorations-zindex: 998; // then doc decorations appear over everything else $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right? $COLLECTION_BORDER_WIDTH: 1; +$MINIMIZED_ICON_SIZE:25; +$MAX_ROW_HEIGHT: 44px; :export { contextMenuZindex: $contextMenu-zindex; COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH; + MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE; + MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT; }
\ No newline at end of file diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts index e874b815d..9788d31f7 100644 --- a/src/client/views/globalCssVariables.scss.d.ts +++ b/src/client/views/globalCssVariables.scss.d.ts @@ -1,7 +1,9 @@ interface IGlobalScss { contextMenuZindex: string; // context menu shows up over everything - COLLECTION_BORDER_WIDTH: number; + COLLECTION_BORDER_WIDTH: string; + MINIMIZED_ICON_SIZE: string; + MAX_ROW_HEIGHT: string; } declare const globalCssVariables: IGlobalScss; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 8cf7a0dd2..1d42b3899 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -17,7 +17,7 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr @computed get transform(): string { - return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `; + return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `; } @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); } @@ -45,14 +45,20 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr this.props.Document.SetData(KeyStore.ZIndex, h, NumberField); } + get X() { + return this.props.Document.GetNumber(KeyStore.X, 0); + } + get Y() { + return this.props.Document.GetNumber(KeyStore.Y, 0); + } getTransform = (): Transform => this.props.ScreenToLocalTransform() - .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0)) + .translate(-this.X, -this.Y) .scale(1 / this.contentScaling()).scale(1 / this.zoom) contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1; - panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth(); - panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight(); + panelWidth = () => this.props.PanelWidth(); + panelHeight = () => this.props.PanelHeight(); @computed get docView() { @@ -66,22 +72,22 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr render() { let zoomFade = 1; - // //var zoom = doc.GetNumber(KeyStore.Zoom, 1); - // let transform = (this.props.ScreenToLocalTransform().scale(this.props.ContentScaling())).inverse(); + //var zoom = doc.GetNumber(KeyStore.Zoom, 1); + // let transform = this.getTransform().scale(this.contentScaling()).inverse(); // var [sptX, sptY] = transform.transformPoint(0, 0); // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight()); // let w = bptX - sptX; // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1; // let fadeUp = .75 * 1800; // let fadeDown = .075 * 1800; - // zoomFade = w < fadeDown || w > fadeUp ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; + // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1; return ( <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{ opacity: zoomFade, transformOrigin: "left top", transform: this.transform, - pointerEvents: "all", + pointerEvents: (zoomFade < 0.09 ? "none" : "all"), width: this.width, height: this.height, position: "absolute", diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 76f852601..07599c345 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -15,6 +15,7 @@ import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; +import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; import { VideoBox } from "./VideoBox"; @@ -62,7 +63,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { return <p>Error loading layout keys</p>; } return <JsxParser - components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} + components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }} bindings={this.CreateBindings()} jsx={this.layout} showWarnings={true} diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 690ee50e8..7c72fb6e6 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -4,7 +4,7 @@ position: inherit; top: 0; left:0; - background: $light-color; //overflow: hidden; + // background: $light-color; //overflow: hidden; transform-origin: left top; &.minimized { @@ -13,7 +13,6 @@ } .top { - background: #232323; height: 20px; cursor: pointer; } @@ -31,18 +30,4 @@ } .documentView-node-topmost { background: white; -} - -.minimized-box { - height: 10px; - width: 10px; - border-radius: 2px; - background: $dark-color; - transform-origin: left top; -} - -.minimized-box:hover { - background: $main-accent; - transform: scale(1.15); - cursor: pointer; }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4f7909692..c06fe4c8d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -36,12 +36,11 @@ export interface DocumentViewProps { focus: (doc: Document) => void; selectOnLoad: boolean; parentActive: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; } const schema = createSchema({ layout: "string", - minimized: "boolean", nativeWidth: "number", nativeHeight: "number", backgroundColor: "string" @@ -136,7 +135,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - if (!this.topMost || e.buttons === 2 || e.altKey) { + if (!e.altKey && (!this.topMost || e.buttons === 2)) { this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey); } } @@ -147,10 +146,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); e.stopPropagation(); - if (!SelectionManager.IsSelected(this) && e.button !== 2 && - Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { - SelectionManager.SelectDoc(this, e.ctrlKey); - } + if (!SelectionManager.IsSelected(this) && e.button !== 2) + if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { + if (this.props.Document.Get(KeyStore.MaximizedDoc) instanceof Document) { + this.props.Document.GetAsync(KeyStore.MaximizedDoc, maxdoc => { + if (maxdoc instanceof Document) { + this.props.addDocument && this.props.addDocument(maxdoc, false); + this.toggleMinimize(maxdoc, this.props.Document); + } + }); + } else { + SelectionManager.SelectDoc(this, e.ctrlKey); + } + } } stopPropagation = (e: React.SyntheticEvent) => { e.stopPropagation(); @@ -179,10 +187,84 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } + @action createIcon = (layoutString: string): Document => { + let iconDoc = Documents.IconDocument(layoutString); + iconDoc.SetBoolean(KeyStore.IsMinimized, false); + iconDoc.SetNumber(KeyStore.NativeWidth, 0); + iconDoc.SetNumber(KeyStore.NativeHeight, 0); + iconDoc.Set(KeyStore.Prototype, this.props.Document); + iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document); + this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc); + this.props.addDocument && this.props.addDocument(iconDoc, false); + return iconDoc; + } + + animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) { + setTimeout(() => { + let now = Date.now(); + let progress = Math.min(1, (now - stime) / 200); + let pval = maximizing ? + [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] : + [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress]; + target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress); + target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress); + target.SetNumber(KeyStore.X, pval[0]); + target.SetNumber(KeyStore.Y, pval[1]); + if (now < stime + 200) { + this.animateTransition(icon, targ, width, height, stime, target, maximizing); + } + else { + if (!maximizing) { + target.SetBoolean(KeyStore.IsMinimized, true); + target.SetNumber(KeyStore.X, targ[0]); + target.SetNumber(KeyStore.Y, targ[1]); + target.SetNumber(KeyStore.Width, width); + target.SetNumber(KeyStore.Height, height); + } + this._completed = true; + } + }, + 2); + } + + _completed = true; + @action - public minimize = (): void => { - this.props.Document.SetBoolean(KeyStore.Minimized, true); + public toggleMinimize = (maximized: Document, minim: Document): void => { SelectionManager.DeselectAll(); + if (maximized instanceof Document && this._completed) { + this._completed = false; + let minimized = maximized.GetBoolean(KeyStore.IsMinimized, false); + maximized.SetBoolean(KeyStore.IsMinimized, false); + this.animateTransition( + [minim.GetNumber(KeyStore.X, 0), minim.GetNumber(KeyStore.Y, 0)], + [maximized.GetNumber(KeyStore.X, 0), maximized.GetNumber(KeyStore.Y, 0)], + maximized.GetNumber(KeyStore.Width, 0), maximized.GetNumber(KeyStore.Height, 0), + Date.now(), maximized, minimized); + } + } + + @action + public minimize = (): void => { + this.props.Document.GetAsync(KeyStore.MinimizedDoc, mindoc => { + if (mindoc === undefined) { + this.props.Document.GetAsync(KeyStore.BackgroundLayout, field => { + if (field instanceof TextField) { + this.toggleMinimize(this.props.Document, this.createIcon(field.Data)); + } + else this.props.Document.GetAsync(KeyStore.Layout, field => { + if (field instanceof TextField) { + this.createIcon(field.Data); + this.toggleMinimize(this.props.Document, this.createIcon(field.Data)); + } + }); + }); + } + else if (mindoc instanceof Document) { + this.props.addDocument && this.props.addDocument(mindoc, false); + this.toggleMinimize(this.props.Document, mindoc); + } + }); } @undoBatch @@ -250,7 +332,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } e.preventDefault(); - !this.isMinimized() && ContextMenu.Instance.addItem({ description: "Minimize", event: this.minimize }); ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }); ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked }); ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) }); @@ -265,9 +346,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } - @action - expand = () => this.Document.minimized = false - isMinimized = () => FieldValue(this.Document.minimized) || false; isSelected = () => SelectionManager.IsSelected(this); select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed); @@ -275,15 +353,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight) || 0; } @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); } + render() { var scaling = this.props.ContentScaling(); var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%"; var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%"; - if (this.isMinimized()) { - return <div className="minimized-box" ref={this._mainCont} onClick={this.expand} onDrop={this.onDrop} - style={{ transform: `scale(${scaling} , ${scaling})` }} onPointerDown={this.onPointerDown} />; - } return ( <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`} ref={this._mainCont} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ebd25f937..93e385821 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -23,6 +23,8 @@ import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../. import { CollectionView } from "../collections/CollectionView"; import { CollectionPDFView } from "../collections/CollectionPDFView"; import { CollectionVideoView } from "../collections/CollectionVideoView"; +import { IconField } from "../../../fields/IconFIeld"; +import { IconBox } from "./IconBox"; // @@ -43,7 +45,7 @@ export interface FieldViewProps { moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; ScreenToLocalTransform: () => Transform; active: () => boolean; - onActiveChanged: (isActive: boolean) => void; + whenActiveChanged: (isActive: boolean) => void; focus: (doc: Document) => void; } @@ -72,6 +74,9 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof ImageField) { return <ImageBox {...this.props} />; } + else if (field instanceof IconField) { + return <IconBox {...this.props} />; + } else if (field instanceof VideoField) { return <VideoBox {...this.props} />; } @@ -95,7 +100,7 @@ export class FieldView extends React.Component<FieldViewProps> { layoutKey={KeyStore.Layout} ContainingCollectionView={this.props.ContainingCollectionView} parentActive={this.props.active} - onActiveChanged={this.props.onActiveChanged} /> + whenActiveChanged={this.props.whenActiveChanged} /> ); } else if (field instanceof ListField) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index bff8ca7a4..639dae30a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -19,6 +19,7 @@ import { MainOverlayTextBox } from "../MainOverlayTextBox"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); +import { SelectionManager } from "../../util/SelectionManager"; const { buildMenuItems } = require("prosemirror-example-setup"); const { menuBar } = require("prosemirror-menu"); @@ -251,6 +252,9 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } onKeyPress(e: React.KeyboardEvent) { + if (e.key == "Escape") { + SelectionManager.DeselectAll(); + } e.stopPropagation(); if (e.keyCode === 9) e.preventDefault(); // stop propagation doesn't seem to stop propagation of native keyboard events. diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss new file mode 100644 index 000000000..ce0ee2e09 --- /dev/null +++ b/src/client/views/nodes/IconBox.scss @@ -0,0 +1,12 @@ + +@import "../globalCssVariables"; +.iconBox-container { + position: absolute; + left:0; + top:0; + svg { + width: 100% !important; + height: 100%; + background: white; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx new file mode 100644 index 000000000..9c90c0a0e --- /dev/null +++ b/src/client/views/nodes/IconBox.tsx @@ -0,0 +1,45 @@ +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 { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from '../../../fields/Document'; +import { IconField } from "../../../fields/IconFIeld"; +import { KeyStore } from "../../../fields/KeyStore"; +import { SelectionManager } from "../../util/SelectionManager"; +import { FieldView, FieldViewProps } from './FieldView'; +import "./IconBox.scss"; + + +library.add(faCaretUp); +library.add(faObjectGroup); +library.add(faStickyNote); +library.add(faFilePdf); +library.add(faFilm); + +@observer +export class IconBox extends React.Component<FieldViewProps> { + public static LayoutString() { return FieldView.LayoutString(IconBox); } + + @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); } + @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); } + @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); } + + 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" /> + } + + render() { + return ( + <div className="iconBox-container"> + {this.minimizedIcon} + </div>); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 71b431b84..fd5381b77 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -38,7 +38,10 @@ export class ImageBox extends React.Component<FieldViewProps> { onLoad = (target: any) => { var h = this._imgRef.current!.naturalHeight; var w = this._imgRef.current!.naturalWidth; - if (this._photoIndex === 0) this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w); + if (this._photoIndex === 0) { + this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w); + this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w); + } } @@ -65,7 +68,7 @@ export class ImageBox extends React.Component<FieldViewProps> { if (layout.indexOf(ImageBox.name) !== -1) { let imgData = this.props.Document.Get(KeyStore.Data); if (imgData instanceof ImageField && imgData) { - this.props.Document.Set(KeyStore.Data, new ListField([imgData])); + this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData])); } let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]); if (imgList) { @@ -137,6 +140,7 @@ export class ImageBox extends React.Component<FieldViewProps> { @action onDotDown(index: number) { this._photoIndex = index; + this.props.Document.SetNumber(KeyStore.CurPage, index); } dots(paths: string[]) { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 5d69f23b2..d480eb5af 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -48,7 +48,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { isTopMost: false, selectOnLoad: false, active: returnFalse, - onActiveChanged: emptyFunction, + whenActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, focus: emptyDocFunction, }; diff --git a/src/fields/IconFIeld.ts b/src/fields/IconFIeld.ts new file mode 100644 index 000000000..a6694cc49 --- /dev/null +++ b/src/fields/IconFIeld.ts @@ -0,0 +1,25 @@ +import { BasicField } from "./BasicField"; +import { FieldId } from "./Field"; +import { Types } from "../server/Message"; + +export class IconField extends BasicField<string> { + constructor(data: string = "", id?: FieldId, save: boolean = true) { + super(data, save, id); + } + + ToScriptString(): string { + return `new IconField("${this.Data}")`; + } + + Copy() { + return new IconField(this.Data); + } + + ToJson() { + return { + type: Types.Icon, + data: this.Data, + id: this.Id + }; + } +}
\ No newline at end of file diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts index 27b9635af..1607d4c15 100644 --- a/src/new_fields/Schema.ts +++ b/src/new_fields/Schema.ts @@ -1,8 +1,8 @@ import { Interface, ToInterface, Cast, FieldCtor, ToConstructor } from "./Types"; import { Doc } from "./Doc"; -export type makeInterface<T extends Interface> = Partial<ToInterface<T>> & Doc; -export function makeInterface<T extends Interface>(schema: T): (doc: Doc) => makeInterface<T> { +export type makeInterface<T extends Interface, U extends Doc = Doc> = Partial<ToInterface<T>> & U; +export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U> { return function (doc: any) { return new Proxy(doc, { get(target, prop) { diff --git a/src/server/Message.ts b/src/server/Message.ts index 843a923d1..b01934724 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -14,7 +14,7 @@ export class Message<T> { } export enum Types { - Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference, + Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference, Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, } diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts index 818230c1a..79ca5e55d 100644 --- a/src/server/ServerUtil.ts +++ b/src/server/ServerUtil.ts @@ -18,6 +18,7 @@ import { NumberField } from "./../fields/NumberField"; import { RichTextField } from "./../fields/RichTextField"; import { TextField } from "./../fields/TextField"; import { Transferable, Types } from "./Message"; +import { IconField } from "../fields/IconFIeld"; export class ServerUtils { public static prepend(extension: string): string { @@ -37,6 +38,7 @@ export class ServerUtils { case Types.Boolean: return new BooleanField(json.data, json.id, false); case Types.Number: return new NumberField(json.data, json.id, false); case Types.Text: return new TextField(json.data, json.id, false); + case Types.Icon: return new IconField(json.data, json.id, false); case Types.Html: return new HtmlField(json.data, json.id, false); case Types.Web: return new WebField(new URL(json.data), json.id, false); case Types.RichText: return new RichTextField(json.data, json.id, false); |