From 5ed2968c5d3e62f06b6355c33d3cb17e9828db26 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 8 Oct 2019 16:50:14 -0400 Subject: basic interactions, not very robust --- src/client/util/InteractionUtils.ts | 109 ++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/client/util/InteractionUtils.ts (limited to 'src/client/util') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts new file mode 100644 index 000000000..f8088825a --- /dev/null +++ b/src/client/util/InteractionUtils.ts @@ -0,0 +1,109 @@ +export namespace InteractionUtils { + export const MOUSE = "mouse"; + export const TOUCH = "touch"; + + export function IsType(e: PointerEvent, type: PointerTypes): boolean { + return e.pointerType === type; + } + + export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number { + return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2)); + } + + /** + * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point) + * @param pts - n-arbitrary long list of points + */ + export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } { + let centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length; + let centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length; + return { X: centerX, Y: centerY }; + } + + /** + * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in + * @param pt1 - new point that corresponds to oldPoint1 + * @param pt2 - new point that corresponds to oldPoint2 + * @param oldPoint1 - previous point 1 + * @param oldPoint2 - previous point 2 + */ + export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { + const leniency = 10; + let dist1 = TwoPointEuclidist(oldPoint1, pt1) + leniency; + let dist2 = TwoPointEuclidist(oldPoint2, pt2) + leniency; + + if (Math.sign(dist1) === Math.sign(dist2)) { + let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); + let newDist = TwoPointEuclidist(pt1, pt2); + return Math.sign(oldDist - newDist); + } + return 0; + } + + /** + * Returns the type of Touch Interaction from a list of points. + * Also returns any data that is associated with a Touch Interaction + * @param pts - List of points + */ + // export function InterpretPointers(pts: React.Touch[]): { type: Opt, data?: any } { + // const leniency = 200; + // switch (pts.length) { + // case 1: + // return { type: OneFinger }; + // case 2: + // return { type: TwoSeperateFingers }; + // case 3: + // let pt1 = pts[0]; + // let pt2 = pts[1]; + // let pt3 = pts[2]; + // if (pt1 && pt2 && pt3) { + // let dist12 = TwoPointEuclidist(pt1, pt2); + // let dist23 = TwoPointEuclidist(pt2, pt3); + // let dist13 = TwoPointEuclidist(pt1, pt3); + // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`); + // let dist12close = dist12 < leniency; + // let dist23close = dist23 < leniency; + // let dist13close = dist13 < leniency; + // let xor2313 = dist23close ? !dist13close : dist13close; + // let xor = dist12close ? !xor2313 : xor2313; + // // three input xor because javascript doesn't have logical xor's + // if (xor) { + // let points: number[] = []; + // let min = Math.min(dist12, dist23, dist13); + // switch (min) { + // case dist12: + // points = [0, 1, 2]; + // break; + // case dist23: + // points = [1, 2, 0]; + // break; + // case dist13: + // points = [0, 2, 1]; + // break; + // } + // return { type: TwoToOneFingers, data: points }; + // } + // else { + // return { type: ThreeSeperateFingers, data: null }; + // } + // } + // default: + // return { type: undefined }; + // } + // } + + export function IsDragging(oldTouches: Map, newTouches: TouchList, leniency: number): boolean { + for (let i = 0; i < newTouches.length; i++) { + let touch = newTouches.item(i); + if (touch) { + let oldTouch = oldTouches.get(touch.identifier); + if (oldTouch) { + if (TwoPointEuclidist(touch, oldTouch) >= leniency) { + return true; + } + } + } + } + return false; + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From b29832a0b75e91f7d53e3820b12d517e6bf3ee94 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 8 Oct 2019 18:14:09 -0400 Subject: touchable added baseline --- src/client/util/InteractionUtils.ts | 2 +- src/client/views/DocComponent.tsx | 3 +- src/client/views/Touchable.tsx | 82 ++++++++++++++ .../views/collections/CollectionBaseView.tsx | 3 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 122 ++++++--------------- src/client/views/nodes/DocumentView.tsx | 3 +- 6 files changed, 125 insertions(+), 90 deletions(-) create mode 100644 src/client/views/Touchable.tsx (limited to 'src/client/util') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index f8088825a..63cba4982 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -2,7 +2,7 @@ export namespace InteractionUtils { export const MOUSE = "mouse"; export const TOUCH = "touch"; - export function IsType(e: PointerEvent, type: PointerTypes): boolean { + export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean { return e.pointerType === type; } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index d6562492f..6d51122d4 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { Doc } from '../../new_fields/Doc'; import { computed } from 'mobx'; +import { Touchable } from './Touchable'; export function DocComponent

(schemaCtor: (doc: Doc) => T) { - class Component extends React.Component

{ + class Component extends Touchable

{ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx new file mode 100644 index 000000000..e9671ab8b --- /dev/null +++ b/src/client/views/Touchable.tsx @@ -0,0 +1,82 @@ +import * as React from 'react'; +import { action } from 'mobx'; +import { InteractionUtils } from '../util/InteractionUtils'; + +export abstract class Touchable extends React.Component { + protected _touchDrag: boolean = false; + protected prevPoints: Map = new Map(); + + public FirstX: number = 0; + public FirstY: number = 0; + public SecondX: number = 0; + public SecondY: number = 0; + + /** + * When a touch even starts, we keep track of each touch that is associated with that event + */ + @action + protected onTouchStart = (e: React.TouchEvent): void => { + for (let i = 0; i < e.targetTouches.length; i++) { + let pt = e.targetTouches.item(i); + this.prevPoints.set(pt.identifier, pt); + } + document.removeEventListener("touchmove", this.onTouch); + document.addEventListener("touchmove", this.onTouch); + document.removeEventListener("touchend", this.onTouchEnd); + document.addEventListener("touchend", this.onTouchEnd); + } + + /** + * Handle touch move event + */ + @action + protected onTouch = (e: TouchEvent): void => { + // if we're not actually moving a lot, don't consider it as dragging yet + if (!InteractionUtils.IsDragging(this.prevPoints, e.targetTouches, 5) && !this._touchDrag) return; + this._touchDrag = true; + switch (e.targetTouches.length) { + case 1: + this.handle1Pointer(e) + break; + case 2: + this.handle2Pointers(e); + break; + } + } + + @action + protected onTouchEnd = (e: TouchEvent): void => { + this._touchDrag = false; + e.stopPropagation(); + + // remove all the touches associated with the event + for (let i = 0; i < e.targetTouches.length; i++) { + let pt = e.targetTouches.item(i); + if (pt) { + if (this.prevPoints.has(pt.identifier)) { + this.prevPoints.delete(pt.identifier); + } + } + } + + if (e.targetTouches.length === 0) { + this.prevPoints.clear(); + } + this.cleanUpInteractions(); + } + + cleanUpInteractions = (): void => { + document.removeEventListener("touchmove", this.onTouch); + document.removeEventListener("touchend", this.onTouchEnd); + } + + handle1Pointer = (e: TouchEvent): any => { + e.stopPropagation(); + e.preventDefault(); + } + + handle2Pointers = (e: TouchEvent): any => { + e.stopPropagation(); + e.preventDefault(); + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 38d050e5c..f8988338f 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -13,6 +13,7 @@ import { FieldViewProps } from '../nodes/FieldView'; import './CollectionBaseView.scss'; import { DateField } from '../../../new_fields/DateField'; import { ImageField } from '../../../new_fields/URLField'; +import { Touchable } from '../Touchable'; export enum CollectionViewType { Invalid, @@ -60,7 +61,7 @@ export interface CollectionViewProps extends FieldViewProps { } @observer -export class CollectionBaseView extends React.Component { +export class CollectionBaseView extends Touchable { @observable private static _safeMode = false; static InSafeMode() { return this._safeMode; } static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 314ce5cdb..2f9d2606f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -351,100 +351,50 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - private prevPoints: Map = new Map(); - public FirstX: number = 0; - public FirstY: number = 0; - public SecondX: number = 0; - public SecondY: number = 0; - - private _touchDrag: boolean = false; - - /** - * When a touch even starts, we keep track of each touch that is associated with that event - */ - @action - onTouchStart = (e: React.TouchEvent): void => { - for (let i = 0; i < e.targetTouches.length; i++) { - let pt = e.targetTouches.item(i); - this.prevPoints.set(pt.identifier, pt); + handle1Pointer = (e: TouchEvent) => { + // panning a workspace + if (!e.cancelBubble && this.props.active()) { + let pt = e.targetTouches.item(0); + if (pt) { + this.pan(pt); + } + e.stopPropagation(); + e.preventDefault(); } - document.removeEventListener("touchmove", this.onTouch); - document.addEventListener("touchmove", this.onTouch); - document.removeEventListener("touchend", this.onTouchEnd); - document.addEventListener("touchend", this.onTouchEnd); } - /** - * Handle touch move event - */ - @action - onTouch = (e: TouchEvent): void => { - // if we're not actually moving a lot, don't consider it as dragging yet - if (!InteractionUtils.IsDragging(this.prevPoints, e.targetTouches, 5) && !this._touchDrag) return; - this._touchDrag = true; - switch (e.targetTouches.length) { - case 1: - // panning a workspace - if (!e.cancelBubble && this.props.active()) { - let pt = e.targetTouches.item(0); - if (pt) { - this.pan(pt); - } - e.stopPropagation(); - e.preventDefault(); - } - break; - case 2: - // pinch zooming - if (!e.cancelBubble) { - let pt1: Touch | null = e.targetTouches.item(0); - let pt2: Touch | null = e.targetTouches.item(1); - if (!pt1 || !pt2) return; - - if (this.prevPoints.size === 2) { - let oldPoint1 = this.prevPoints.get(pt1.identifier); - let oldPoint2 = this.prevPoints.get(pt2.identifier); - if (oldPoint1 && oldPoint2) { - let dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2); - - // if zooming, zoom - if (dir !== 0) { - let d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2)); - let d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); - let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; - let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - let delta = dir * (d1 + d2); - this.zoom(centerX, centerY, delta, 250); - this.prevPoints.set(pt1.identifier, pt1); - this.prevPoints.set(pt2.identifier, pt2); - } - } + handle2Pointers = (e: TouchEvent) => { + // pinch zooming + if (!e.cancelBubble) { + let pt1: Touch | null = e.targetTouches.item(0); + let pt2: Touch | null = e.targetTouches.item(1); + if (!pt1 || !pt2) return; + + if (this.prevPoints.size === 2) { + let oldPoint1 = this.prevPoints.get(pt1.identifier); + let oldPoint2 = this.prevPoints.get(pt2.identifier); + if (oldPoint1 && oldPoint2) { + let dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2); + + // if zooming, zoom + if (dir !== 0) { + let d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2)); + let d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); + let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; + let delta = dir * (d1 + d2); + this.zoom(centerX, centerY, delta, 250); + this.prevPoints.set(pt1.identifier, pt1); + this.prevPoints.set(pt2.identifier, pt2); } } - e.stopPropagation(); - e.preventDefault(); - break; - } - } - - @action - onTouchEnd = (e: TouchEvent): void => { - this._touchDrag = false; - e.stopPropagation(); - - // remove all the touches associated with the event - for (let i = 0; i < e.targetTouches.length; i++) { - let pt = e.targetTouches.item(i); - if (pt) { - if (this.prevPoints.has(pt.identifier)) { - this.prevPoints.delete(pt.identifier); - } } } + e.stopPropagation(); + e.preventDefault(); + } - if (e.targetTouches.length === 0) { - this.prevPoints.clear(); - } + cleanUpInteractions = () => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.removeEventListener("touchmove", this.onTouch); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6627b8792..bf90e2d08 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -611,8 +611,9 @@ export class DocumentView extends DocComponent(Docu DataDoc={this.props.DataDoc} />); } - onTouchStart = (e: React.TouchEvent) => { + handle1Pointer = (e: TouchEvent) => { e.stopPropagation(); + e.preventDefault(); } render() { -- cgit v1.2.3-70-g09d2 From bedfd7e30c4c331628968ea62fef736a1d2cd542 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 10:35:11 -0400 Subject: added manager for managing brush tool state on rich text editor + updated styling to match more with pdf --- src/client/util/TooltipTextMenu.scss | 1006 ++++++++++++++------ src/client/util/TooltipTextMenu.tsx | 566 +++++++---- .../buxton/source/.Bill_Notes_NewO.docx.icloud | Bin 172 -> 0 bytes .../buxton/source/.Bill_Notes_OLPC.docx.icloud | Bin 172 -> 0 bytes 4 files changed, 1112 insertions(+), 460 deletions(-) delete mode 100644 src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud delete mode 100644 src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index ebf833dbe..6bafdbbfd 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,100 +1,68 @@ @import "../views/globalCssVariables"; - -.ProseMirror-textblock-dropdown { - min-width: 3em; - } - - .ProseMirror-menu { - margin: 0 -4px; - line-height: 1; - } - - .ProseMirror-tooltip .ProseMirror-menu { - width: -webkit-fit-content; - width: fit-content; - white-space: pre; - } - - .ProseMirror-menuitem { - margin-right: 3px; +.ProseMirror-menu-dropdown-wrap { + // margin: 0 4px; display: inline-block; - z-index: 50000; position: relative; - } - - .ProseMirror-menuseparator { - // border-right: 1px solid #ddd; - margin-right: 3px; - } - - .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { - font-size: 90%; - white-space: nowrap; - } +} - .ProseMirror-menu-dropdown { +.ProseMirror-menu-dropdown { vertical-align: 1px; cursor: pointer; position: relative; - padding-right: 15px; - margin: 3px; + padding: 0 15px 0 4px; background: white; - border-radius: 3px; - text-align: center; - } - - .ProseMirror-menu-dropdown-wrap { - padding: 1px 0 1px 4px; - display: inline-block; + border-radius: 2px; + text-align: left; + font-size: 12px; + white-space: nowrap; + margin-right: 4px; + + &:after { + content: ""; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 2px); + } +} + +.ProseMirror-menu-submenu-wrap { position: relative; - } - - .ProseMirror-menu-dropdown:after { - content: ""; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid currentColor; - opacity: .6; - position: absolute; - right: 4px; - top: calc(50% - 2px); - } - - .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { - background: $dark-color; - color:white; + margin-right: -4px; +} + +.ProseMirror-menu-dropdown-menu, +.ProseMirror-menu-submenu { + font-size: 12px; + background: white; border: 1px solid rgb(223, 223, 223); - padding: 2px; - } - - .ProseMirror-menu-dropdown-menu { + min-width: 50px; z-index: 50000; - min-width: 6em; - background: white; position: absolute; - } - - .linking { - text-align: center; - } - .ProseMirror-menu-dropdown-item { - cursor: pointer; - padding: 2px 8px 2px 4px; - width: auto; - z-index: 100000; - } - - .ProseMirror-menu-dropdown-item:hover { - background: white; - } - - .ProseMirror-menu-submenu-wrap { - position: relative; - margin-right: -4px; - } - - .ProseMirror-menu-submenu-label:after { + .ProseMirror-menu-dropdown-item { + cursor: pointer; + z-index: 100000; + text-align: left; + padding: 3px; + + &:hover { + background-color: $light-color-secondary; + } + } +} + +// .ProseMirror-menu-submenu { +// display: none; +// min-width: 4em; +// left: 100%; +// top: -3px; +// } + +.ProseMirror-menu-submenu-label:after { content: ""; border-top: 4px solid transparent; border-bottom: 4px solid transparent; @@ -103,153 +71,144 @@ position: absolute; right: 4px; top: calc(50% - 4px); - } - - .ProseMirror-menu-submenu { - display: none; - min-width: 4em; - left: 100%; - top: -3px; - } - - .ProseMirror-menu-active { - background: #eee; - border-radius: 4px; - } - - .ProseMirror-menu-active { - background: #eee; - border-radius: 4px; - } - - .ProseMirror-menu-disabled { - opacity: .3; - } - - .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { - display: block; - } +} + + - .ProseMirror-menubar { - border-top-left-radius: inherit; - border-top-right-radius: inherit; - position: relative; - min-height: 1em; - color: white; - padding: 10px 10px; - top: 0; left: 0; right: 0; - border-bottom: 1px solid silver; - background:$dark-color; - z-index: 10; - -moz-box-sizing: border-box; - box-sizing: border-box; - overflow: visible; - } +// .ProseMirror-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-disabled { +// opacity: .3; +// } + + +// .ProseMirror-menubar { +// border-top-left-radius: inherit; +// border-top-right-radius: inherit; +// position: relative; +// min-height: 1em; +// color: white; +// padding: 10px 10px; +// top: 0; left: 0; right: 0; +// border-bottom: 1px solid silver; +// background:$dark-color; +// z-index: 10; +// -moz-box-sizing: border-box; +// box-sizing: border-box; +// overflow: visible; +// } .ProseMirror-icon { display: inline-block; - line-height: .8; - vertical-align: -2px; /* Compensate for padding */ - padding: 2px 8px; + // line-height: .8; + // vertical-align: -2px; /* Compensate for padding */ + // padding: 2px 8px; cursor: pointer; - } - - .ProseMirror-menu-disabled.ProseMirror-icon { - cursor: default; - } - - .ProseMirror-icon svg { - fill:white; - height: 1em; - } - - .ProseMirror-icon span { - vertical-align: text-top; - } - - .ProseMirror ul, .ProseMirror ol { - padding-left: 30px; - } - - .ProseMirror blockquote { - padding-left: 1em; - border-left: 3px solid #eee; - margin-left: 0; margin-right: 0; - } - - .ProseMirror-example-setup-style img { - cursor: default; - } - - .ProseMirror-prompt { - background: white; - padding: 5px 10px 5px 15px; - border: 1px solid silver; - position: fixed; - border-radius: 3px; - z-index: 11; - box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); - } - - .ProseMirror-prompt h5 { - margin: 0; - font-weight: normal; - font-size: 100%; - color: #444; - } - - .ProseMirror-prompt input[type="text"], - .ProseMirror-prompt textarea { - background: white; - border: none; - outline: none; - } - - .ProseMirror-prompt input[type="text"] { - padding: 0 4px; - } - - .ProseMirror-prompt-close { - position: absolute; - left: 2px; top: 1px; - color: #666; - border: none; background: transparent; padding: 0; - } - - .ProseMirror-prompt-close:after { - content: "✕"; - font-size: 12px; - } - - .ProseMirror-invalid { - background: #ffc; - border: 1px solid #cc7; - border-radius: 4px; - padding: 5px 10px; + + &.ProseMirror-menu-disabled { + cursor: default; + } + + svg { + fill:white; + height: 1em; + } + + span { + vertical-align: text-top; + } + } + +// .ProseMirror ul, .ProseMirror ol { +// padding-left: 30px; +// } + +// .ProseMirror blockquote { +// padding-left: 1em; +// border-left: 3px solid #eee; +// margin-left: 0; margin-right: 0; +// } + +// .ProseMirror-example-setup-style img { +// cursor: default; +// } + +// .ProseMirror-prompt { +// background: white; +// padding: 5px 10px 5px 15px; +// border: 1px solid silver; +// position: fixed; +// border-radius: 3px; +// z-index: 11; +// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); +// } + +// .ProseMirror-prompt h5 { +// margin: 0; +// font-weight: normal; +// font-size: 100%; +// color: #444; +// } + +// .ProseMirror-prompt input[type="text"], +// .ProseMirror-prompt textarea { +// background: white; +// border: none; +// outline: none; +// } + +// .ProseMirror-prompt input[type="text"] { +// padding: 0 4px; +// } + +// .ProseMirror-prompt-close { +// position: absolute; +// left: 2px; top: 1px; +// color: #666; +// border: none; background: transparent; padding: 0; +// } + +// .ProseMirror-prompt-close:after { +// content: "✕"; +// font-size: 12px; +// } + +// .ProseMirror-invalid { +// background: #ffc; +// border: 1px solid #cc7; +// border-radius: 4px; +// padding: 5px 10px; +// position: absolute; +// min-width: 10em; +// } + +// .ProseMirror-prompt-buttons { +// margin-top: 5px; +// display: none; +// } + +.wrapper { position: absolute; - min-width: 10em; - } - - .ProseMirror-prompt-buttons { - margin-top: 5px; - display: none; - } + pointer-events: all; +} .tooltipMenu { position: absolute; z-index: 20000; - background: #121721; - border: 1px solid silver; - border-radius: 15px; - //height: 60px; - //padding: 2px 10px; - //margin-top: 100px; - //-webkit-transform: translateX(-50%); - //transform: translateX(-50%); + background: #323232; + border-radius: 6px; transform: translateY(-85px); pointer-events: all; - height: fit-content; - width:550px; + height: 35px; + width: 650px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + display: flex; + align-items: center; + .ProseMirror-example-setup-style hr { padding: 2px 10px; border: none; @@ -265,54 +224,96 @@ } } -.tooltipExtras { - position: absolute; - z-index: 20000; - background: #121721; - border: 1px solid silver; - border-radius: 15px; - //height: 60px; - //padding: 2px 10px; - //margin-top: 100px; - //-webkit-transform: translateX(-50%); - //transform: translateX(-50%); - transform: translateY(-115px); - pointer-events: all; +// .tooltipExtras { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-115px); +// pointer-events: all; +// height: 25px; +// width:fit-content; +// .ProseMirror-example-setup-style hr { +// padding: 2px 10px; +// border: none; +// margin: 1em 0; +// } + +// .ProseMirror-example-setup-style hr:after { +// content: ""; +// display: block; +// height: 1px; +// background-color: silver; +// line-height: 2px; +// } +// } + +#link-drag { + background-color: #323232; +} + +.menuicon { + width: 25px; height: 25px; - width:fit-content; - .ProseMirror-example-setup-style hr { - padding: 2px 10px; - border: none; - margin: 1em 0; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + + #link-drag { + background-color: black; + } } - - .ProseMirror-example-setup-style hr:after { - content: ""; - display: block; - height: 1px; - background-color: silver; - line-height: 2px; + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); } -} -.wrapper { - position: absolute; - pointer-events: all; + svg { + fill: white; + width: 18px; + height: 18px; + } } - .menuicon { - display: inline-block; - border-right: 1px solid white(0, 0, 0, 0.2); - //color: rgb(19, 18, 18); - color: rgb(226, 21, 21); - line-height: 1; - padding: 0px 2px; - margin: 1px; +.menuicon-active { + width: 25px; + height: 25px; cursor: pointer; text-align: center; - min-width: 10px; - - } + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: greenyellow; + width: 18px; + height: 18px; + } +} + .strong, .heading { font-weight: bold; } .em { font-style: italic; } .underline {text-decoration: underline} @@ -328,8 +329,9 @@ height: 20px; text-align: center; } + - .brush{ +.brush{ display: inline-block; width: 1em; height: 1em; @@ -337,19 +339,487 @@ stroke: currentColor; fill: currentColor; margin-right: 15px; - } +} - .brush-active{ +.brush-active{ display: inline-block; width: 1em; height: 1em; stroke-width: 3; - stroke: greenyellow; + // stroke: greenyellow; fill: greenyellow; margin-right: 15px; - } +} + +.dragger { + color: #eee; + margin-left: 5px; + width: 16px; + height: 22px; + display: inline-block; + cursor: grab; + + .dragger-wrapper { + width: 100%; + height: 100%; + display: flex; + justify-content: space-evenly; + } + + .dragger-line { + width: 2px; + height: 100%; + background-color: black; + } +} + +// .menuicon:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown, +// .menuicon-active:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown { +// background-color: black; +// } + +.button-dropdown-wrapper { + display: flex; + align-content: center; + + &:hover { + background-color: black; + } +} + +.buttonSettings-dropdown { + + &.ProseMirror-menu-dropdown { + width: 10px; + height: 25px; + margin: 0; + padding: 0 2px; + background-color: #323232; + text-align: center; + + &:after { + border-top: 4px solid white; + right: 2px; + } + + &:hover { + background-color: black; + } + } + + &.ProseMirror-menu-dropdown-menu { + min-width: 150px; + left: -27px; + top: 31px; + background-color: #323232; + color: $light-color-secondary; + border: none; + // border: 1px solid $light-color-secondary; + border-radius: 0 6px 6px 6px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + + .ProseMirror-menu-dropdown-item{ + cursor: default; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #323232; + } + + .button-setting, .button-setting-disabled { + padding: 2px; + border-radius: 2px; + } + + .button-setting:hover { + cursor: pointer; + background-color: black; + } + + .separated-button { + border-top: 1px solid $light-color-secondary; + padding-top: 6px; + } + + input { + color: black; + border: none; + border-radius: 1px; + padding: 3px; + } + + button { + padding: 6px; + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + + &:hover { + background-color: black; + } + } + } + + + } +} + +// @import "../views/globalCssVariables"; + +// .ProseMirror-textblock-dropdown { +// min-width: 3em; +// } + +// .ProseMirror-menu { +// margin: 0 -4px; +// line-height: 1; +// } + +// .ProseMirror-tooltip .ProseMirror-menu { +// width: -webkit-fit-content; +// width: fit-content; +// white-space: pre; +// } + +// .ProseMirror-menuitem { +// margin-right: 3px; +// display: inline-block; +// z-index: 50000; +// position: relative; +// } + +// .ProseMirror-menuseparator { +// // border-right: 1px solid #ddd; +// margin-right: 3px; +// } + +// .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { +// font-size: 90%; +// white-space: nowrap; +// } + +// .ProseMirror-menu-dropdown { +// vertical-align: 1px; +// cursor: pointer; +// position: relative; +// padding-right: 15px; +// margin: 3px; +// background: white; +// border-radius: 3px; +// text-align: center; +// } + +// .ProseMirror-menu-dropdown-wrap { +// padding: 1px 0 1px 4px; +// display: inline-block; +// position: relative; +// } + +// .ProseMirror-menu-dropdown:after { +// content: ""; +// border-left: 4px solid transparent; +// border-right: 4px solid transparent; +// border-top: 4px solid currentColor; +// opacity: .6; +// position: absolute; +// right: 4px; +// top: calc(50% - 2px); +// } + +// .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { +// background: $dark-color; +// color:white; +// border: 1px solid rgb(223, 223, 223); +// padding: 2px; +// } + +// .ProseMirror-menu-dropdown-menu { +// z-index: 50000; +// min-width: 6em; +// background: white; +// position: absolute; +// } + +// .linking { +// text-align: center; +// } + +// .ProseMirror-menu-dropdown-item { +// cursor: pointer; +// padding: 2px 8px 2px 4px; +// width: auto; +// z-index: 100000; +// } + +// .ProseMirror-menu-dropdown-item:hover { +// background: white; +// } + +// .ProseMirror-menu-submenu-wrap { +// position: relative; +// margin-right: -4px; +// } + +// .ProseMirror-menu-submenu-label:after { +// content: ""; +// border-top: 4px solid transparent; +// border-bottom: 4px solid transparent; +// border-left: 4px solid currentColor; +// opacity: .6; +// position: absolute; +// right: 4px; +// top: calc(50% - 4px); +// } + +// .ProseMirror-menu-submenu { +// display: none; +// min-width: 4em; +// left: 100%; +// top: -3px; +// } + +// .ProseMirror-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-disabled { +// opacity: .3; +// } + +// .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { +// display: block; +// } + +// .ProseMirror-menubar { +// border-top-left-radius: inherit; +// border-top-right-radius: inherit; +// position: relative; +// min-height: 1em; +// color: white; +// padding: 10px 10px; +// top: 0; left: 0; right: 0; +// border-bottom: 1px solid silver; +// background:$dark-color; +// z-index: 10; +// -moz-box-sizing: border-box; +// box-sizing: border-box; +// overflow: visible; +// } + +// .ProseMirror-icon { +// display: inline-block; +// line-height: .8; +// vertical-align: -2px; /* Compensate for padding */ +// padding: 2px 8px; +// cursor: pointer; +// } + +// .ProseMirror-menu-disabled.ProseMirror-icon { +// cursor: default; +// } + +// .ProseMirror-icon svg { +// fill:white; +// height: 1em; +// } + +// .ProseMirror-icon span { +// vertical-align: text-top; +// } + +// .ProseMirror ul, .ProseMirror ol { +// padding-left: 30px; +// } + +// .ProseMirror blockquote { +// padding-left: 1em; +// border-left: 3px solid #eee; +// margin-left: 0; margin-right: 0; +// } + +// .ProseMirror-example-setup-style img { +// cursor: default; +// } + +// .ProseMirror-prompt { +// background: white; +// padding: 5px 10px 5px 15px; +// border: 1px solid silver; +// position: fixed; +// border-radius: 3px; +// z-index: 11; +// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); +// } + +// .ProseMirror-prompt h5 { +// margin: 0; +// font-weight: normal; +// font-size: 100%; +// color: #444; +// } + +// .ProseMirror-prompt input[type="text"], +// .ProseMirror-prompt textarea { +// background: white; +// border: none; +// outline: none; +// } + +// .ProseMirror-prompt input[type="text"] { +// padding: 0 4px; +// } + +// .ProseMirror-prompt-close { +// position: absolute; +// left: 2px; top: 1px; +// color: #666; +// border: none; background: transparent; padding: 0; +// } + +// .ProseMirror-prompt-close:after { +// content: "✕"; +// font-size: 12px; +// } + +// .ProseMirror-invalid { +// background: #ffc; +// border: 1px solid #cc7; +// border-radius: 4px; +// padding: 5px 10px; +// position: absolute; +// min-width: 10em; +// } + +// .ProseMirror-prompt-buttons { +// margin-top: 5px; +// display: none; +// } + +// .tooltipMenu { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-85px); +// pointer-events: all; +// height: fit-content; +// width:550px; +// .ProseMirror-example-setup-style hr { +// padding: 2px 10px; +// border: none; +// margin: 1em 0; +// } + +// .ProseMirror-example-setup-style hr:after { +// content: ""; +// display: block; +// height: 1px; +// background-color: silver; +// line-height: 2px; +// } +// } + +// .tooltipExtras { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-115px); +// pointer-events: all; +// height: 25px; +// width:fit-content; +// .ProseMirror-example-setup-style hr { +// padding: 2px 10px; +// border: none; +// margin: 1em 0; +// } + +// .ProseMirror-example-setup-style hr:after { +// content: ""; +// display: block; +// height: 1px; +// background-color: silver; +// line-height: 2px; +// } +// } + +// .wrapper { +// position: absolute; +// pointer-events: all; +// } + +// .menuicon { +// display: inline-block; +// border-right: 1px solid white(0, 0, 0, 0.2); +// //color: rgb(19, 18, 18); +// color: rgb(226, 21, 21); +// line-height: 1; +// padding: 0px 2px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 10px; + +// } +// .strong, .heading { font-weight: bold; } +// .em { font-style: italic; } +// .underline {text-decoration: underline} +// .superscript {vertical-align:super} +// .subscript { vertical-align:sub } +// .strikethrough {text-decoration-line:line-through} +// .font-size-indicator { +// font-size: 12px; +// padding-right: 0px; +// } +// .summarize{ +// color: white; +// height: 20px; +// text-align: center; +// } + +// .brush{ +// display: inline-block; +// width: 1em; +// height: 1em; +// stroke-width: 0; +// stroke: currentColor; +// fill: currentColor; +// margin-right: 15px; +// } + +// .brush-active{ +// display: inline-block; +// width: 1em; +// height: 1em; +// stroke-width: 3; +// stroke: greenyellow; +// fill: greenyellow; +// margin-right: 15px; +// } - .dragger{ - color: #eee; - margin-left: 5px; - } \ No newline at end of file +// .dragger{ +// color: #eee; +// margin-left: 5px; +// } \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index c82d3bc63..6070d2ef4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -17,79 +17,99 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; +import { SelectionManager } from './SelectionManager'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { - public tooltip: HTMLElement; + // editor state private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private fontStyles: MarkType[]; - private fontSizes: MarkType[]; - private listTypes: (NodeType | any)[]; - private fontSizeToNum: Map; - private fontStylesToName: Map; - private listTypeToIcon: Map; + private fontStyles: MarkType[] = []; + private fontSizes: MarkType[] = []; + private listTypes: (NodeType | any)[] = []; + private fontSizeToNum: Map = new Map(); + private fontStylesToName: Map = new Map(); + private listTypeToIcon: Map = new Map(); + private _activeMarks: Mark[] = []; + private _marksToDoms: Map = new Map(); + // private _brushMarks?: Set; //private link: HTMLAnchorElement; - private wrapper: HTMLDivElement; - private extras: HTMLDivElement; + + // editor doms + public tooltip: HTMLElement = document.createElement("div"); + private wrapper: HTMLDivElement = document.createElement("div"); private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; - //dropdown doms + private _linkDropdownDom?: Node; + private _brushdom?: Node; + private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private _activeMarks: Mark[] = []; - - private _collapseBtn?: MenuItem; + private _collapsed: boolean = false; + // private _collapseBtn?: MenuItem; + // private _brushIsEmpty: boolean = true; - private _brushMarks?: Set; - private _brushIsEmpty: boolean = true; - private _brushdom?: Node; - private _marksToDoms: Map = new Map(); + public static Toolbar: HTMLDivElement | undefined; - private _collapsed: boolean = false; constructor(view: EditorView) { this.view = view; - this.wrapper = document.createElement("div"); - this.tooltip = document.createElement("div"); - this.extras = document.createElement("div"); + // replace old active menu with this + if (TooltipTextMenuManager.Instance.activeMenu) { + TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); + } + TooltipTextMenuManager.Instance.activeMenu = this; - this.wrapper.appendChild(this.extras); - this.wrapper.appendChild(this.tooltip); + // initialize the tooltip + this.createTooltip(view); - this.tooltip.className = "tooltipMenu"; - this.extras.className = "tooltipExtras"; + // initialize the wrapper + this.wrapper = document.createElement("div"); this.wrapper.className = "wrapper"; + this.wrapper.appendChild(this.tooltip); - const dragger = document.createElement("span"); - dragger.className = "dragger"; - dragger.textContent = ">>>"; - this.extras.appendChild(dragger); - - this.dragElement(dragger); + // positioning? + TooltipTextMenu.Toolbar = this.wrapper; - // this.createCollapse(); - // if (this._collapseBtn) { - // this.tooltip.appendChild(this._collapseBtn.render(this.view).dom); + // // position wrapper + // if (TooltipTextMenuManager.Instance.isPinned) { + // this.wrapper.style.position = "absolute"; + // this.wrapper.style.left = TooltipTextMenuManager.Instance.pinnedX + "px"; + // this.wrapper.style.top = TooltipTextMenuManager.Instance.pinnedY + "px"; + + // // if pinned, append to mainview + // const mainView = document.querySelector("#main-div"); + // mainView && mainView.appendChild(this.wrapper); + // } else { + // this.wrapper.style.position = "absolute"; + // this.wrapper.style.top = (this.wrapper.offsetTop + TooltipTextMenuManager.Instance.unpinnedY) + "px"; + // this.wrapper.style.left = (this.wrapper.offsetLeft + TooltipTextMenuManager.Instance.unpinnedX) + "px"; + + // // add tooltip to outerdiv to circumvent scaling problem + // const outer_div = this.editorProps.outer_div; + // outer_div && outer_div(this.wrapper); // } - //add the div which is the tooltip - //view.dom.parentNode!.parentNode!.appendChild(this.tooltip); - //add additional icons - library.add(faListUl); - //add the buttons to the tooltip + } + + private async createTooltip(view: EditorView) { + // initialize element + this.tooltip = document.createElement("div"); + this.tooltip.className = "tooltipMenu"; + + // init buttons to the tooltip let items = [ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, @@ -100,8 +120,8 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; + // add menu items this._marksToDoms = new Map(); - //add menu items items.forEach(({ dom, command }) => { this.tooltip.appendChild(dom); switch (dom.title) { @@ -131,10 +151,46 @@ export class TooltipTextMenu { }); }); + + // link menu this.updateLinkMenu(); + let dropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(this.view).dom; + this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + this._linkDropdownDom = newLinkDropdowndom; + + // list of font styles + this.initFontStyles(); + + // font sizes + this.initFontSizes(); + + // list types + this.initListTypes(); + + // init brush tool + this._brushdom = this.createBrush().render(this.view).dom; + this.tooltip.appendChild(this._brushdom); + // this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; + // this.tooltip.appendChild(this._brushDropdownDom); + + // star and pin + this.tooltip.appendChild(this.createLink().render(this.view).dom); + this.tooltip.appendChild(this.createStar().render(this.view).dom); + // + this.updateListItemDropdown(":", this.listTypeBtnDom); + + // + this.updateFromDash(view, undefined, undefined); + // TooltipTextMenu.Toolbar = this.wrapper; - //list of font styles + // dragger + // TODO: onclick handler in drag handles collapsing + this.createDragger(); + } + + initFontStyles() { this.fontStylesToName = new Map(); this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); this.fontStylesToName.set(schema.marks.arial, "Arial"); @@ -144,8 +200,9 @@ export class TooltipTextMenu { this.fontStylesToName.set(schema.marks.impact, "Impact"); this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); this.fontStyles = Array.from(this.fontStylesToName.keys()); + } - //font sizes + initFontSizes() { this.fontSizeToNum = new Map(); this.fontSizeToNum.set(schema.marks.p10, 10); this.fontSizeToNum.set(schema.marks.p12, 12); @@ -158,39 +215,41 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p48, 48); this.fontSizeToNum.set(schema.marks.p72, 72); this.fontSizeToNum.set(schema.marks.pFontSize, 10); - // this.fontSizeToNum.set(schema.marks.pFontSize, 12); - // this.fontSizeToNum.set(schema.marks.pFontSize, 14); - // this.fontSizeToNum.set(schema.marks.pFontSize, 16); - // this.fontSizeToNum.set(schema.marks.pFontSize, 18); - // this.fontSizeToNum.set(schema.marks.pFontSize, 20); - // this.fontSizeToNum.set(schema.marks.pFontSize, 24); - // this.fontSizeToNum.set(schema.marks.pFontSize, 32); - // this.fontSizeToNum.set(schema.marks.pFontSize, 48); - // this.fontSizeToNum.set(schema.marks.pFontSize, 72); this.fontSizes = Array.from(this.fontSizeToNum.keys()); + } - //list types + initListTypes() { this.listTypeToIcon = new Map(); this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); + } - //custom tools - // this.tooltip.appendChild(this.createLink().render(this.view).dom); + createDragger() { + const dragger = document.createElement("div"); + dragger.className = "dragger"; - this._brushdom = this.createBrush().render(this.view).dom; - this.tooltip.appendChild(this._brushdom); - this.tooltip.appendChild(this.createLink().render(this.view).dom); - this.tooltip.appendChild(this.createStar().render(this.view).dom); + let draggerWrapper = document.createElement("div"); + draggerWrapper.className = "dragger-wrapper"; - this.updateListItemDropdown(":", this.listTypeBtnDom); + let line1 = document.createElement("span"); + line1.className = "dragger-line"; + let line2 = document.createElement("span"); + line2.className = "dragger-line"; + let line3 = document.createElement("span"); + line3.className = "dragger-line"; - this.updateFromDash(view, undefined, undefined); - TooltipTextMenu.Toolbar = this.wrapper; + draggerWrapper.appendChild(line1); + draggerWrapper.appendChild(line2); + draggerWrapper.appendChild(line3); + + dragger.appendChild(draggerWrapper); + + this.tooltip.appendChild(dragger); + this.dragElement(dragger); } - public static Toolbar: HTMLDivElement | undefined; //label of dropdown will change to given label updateFontSizeDropdown(label: string) { @@ -243,24 +302,13 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkEditor.style.color = "black"; - this.linkText = document.createElement("div"); - this.linkText.style.cssFloat = "left"; - this.linkText.style.marginRight = "5px"; - this.linkText.style.marginLeft = "5px"; - this.linkText.setAttribute("contenteditable", "true"); - this.linkText.style.whiteSpace = "nowrap"; - this.linkText.style.width = "150px"; - this.linkText.style.overflow = "hidden"; - this.linkText.style.color = "white"; - this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; - let linkBtn = document.createElement("div"); - linkBtn.textContent = ">>"; - linkBtn.style.width = "10px"; - linkBtn.style.height = "10px"; - linkBtn.style.color = "white"; - linkBtn.style.cssFloat = "left"; - linkBtn.onpointerdown = (e: PointerEvent) => { + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "13px"; + this.linkDrag.style.height = "13px"; + this.linkDrag.title = "Drag to create link"; + this.linkDrag.id = "link-drag"; + this.linkDrag.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); if (link) { @@ -281,62 +329,137 @@ export class TooltipTextMenu { e.preventDefault(); } }; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "15px"; - this.linkDrag.style.height = "15px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.style.color = "black"; - this.linkDrag.style.background = "black"; - this.linkDrag.style.cssFloat = "left"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { - if (!this.editorProps) return; - let dragData = new DragManager.LinkDragData(this.editorProps.Document); - dragData.dontClearTextBox = true; - // hack to get source context -sy - let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); - e.stopPropagation(); - let ctrlKey = e.ctrlKey; - DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, - { - handlers: { - dragComplete: action(() => { - if (dragData.linkDocument) { - let linkDoc = dragData.linkDocument; - let proto = Doc.GetProto(linkDoc); - if (proto && docView) { - proto.sourceContext = docView.props.ContainingCollectionDoc; - } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); - if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { - proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link - } - } - }), - }, - hideSource: false - }); - e.stopPropagation(); - e.preventDefault(); - }; this.linkEditor.appendChild(this.linkDrag); this.tooltip.appendChild(this.linkEditor); } + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // this.linkText.textContent = link ? link.attrs.href : "-empty-"; + + // this.linkText.onkeydown = (e: KeyboardEvent) => { + // if (e.key === "Enter") { + // // this.makeLink(this.linkText!.textContent!); + // e.stopPropagation(); + // e.preventDefault(); + // } + // }; + // // this.tooltip.appendChild(this.linkEditor); + } + + async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - this.linkText.textContent = link ? link.attrs.href : "-empty-"; + if (link) { + let href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + let linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + let anchor1 = await Cast(linkDoc.anchor1, Doc); + let anchor2 = await Cast(linkDoc.anchor2, Doc); + let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); + if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); + } + } + } + } else { + return href; + } + } + } + } - this.linkText.onkeydown = (e: KeyboardEvent) => { - if (e.key === "Enter") { - // this.makeLink(this.linkText!.textContent!); - e.stopPropagation(); - e.preventDefault(); + async createLinkDropdown() { + let targetTitle = await this.getTextLinkTargetTitle(); + let input = document.createElement("input"); + + // menu item for input for hyperlink url + // TODO: integrate search to allow users to search for a doc to link to + let linkInfo = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Linked to:"; + + input.type = "text"; + input.placeholder = "Enter URL"; + if (targetTitle) input.value = targetTitle; + input.onclick = (e: MouseEvent) => { + input.select(); + input.focus(); + }; + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(input); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); } - }; - // this.tooltip.appendChild(this.linkEditor); + }); + + // menu item to update/apply the hyperlink to the selected text + let linkApply = new MenuItem({ + title: "", + execEvent: "", + class: "", + css: "", + render() { + let button = document.createElement("button"); + button.className = "link-url-button"; + button.textContent = "Apply hyperlink"; + return button; + }, + enable() { return false; }, + run: (state, dispatch, view, event) => { + event.stopPropagation(); + this.makeLink(input.value, "onRight"); + } + }); + + // menu item to remove the link + // TODO: allow this to be undoable + let self = this; + let deleteLink = new MenuItem({ + title: "Delete link", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Remove link"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + async run() { + self.deleteLink(); + // update link dropdown + let dropdown = await self.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(self.view).dom; + self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); + self._linkDropdownDom = newLinkDropdowndom; + } + }); + + + let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; + return linkDropdown; } + dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (elmnt) { @@ -627,15 +750,15 @@ export class TooltipTextMenu { // selectionchanged event handler brush_function(state: EditorState, dispatch: any) { - if (this._brushIsEmpty) { + if (TooltipTextMenuManager.Instance._brushIsEmpty) { const selected_marks = this.getMarksInSelection(this.view.state); if (this._brushdom) { if (selected_marks.size >= 0) { - this._brushMarks = selected_marks; + TooltipTextMenuManager.Instance._brushMarks = selected_marks; const newbrush = this.createBrush(true).render(this.view).dom; this.tooltip.replaceChild(newbrush, this._brushdom); this._brushdom = newbrush; - this._brushIsEmpty = !this._brushIsEmpty; + TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty; } } } @@ -643,9 +766,9 @@ export class TooltipTextMenu { let { from, to, $from } = this.view.state.selection; if (this._brushdom) { if (!this.view.state.selection.empty && $from && $from.nodeAfter) { - if (this._brushMarks && to - from > 0) { + if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { + Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; this.changeToMarkInGroup(markType, this.view, []); }); @@ -655,7 +778,7 @@ export class TooltipTextMenu { const newbrush = this.createBrush(false).render(this.view).dom; this.tooltip.replaceChild(newbrush, this._brushdom); this._brushdom = newbrush; - this._brushIsEmpty = !this._brushIsEmpty; + TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty; } } } @@ -663,46 +786,46 @@ export class TooltipTextMenu { } - createCollapse() { - this._collapseBtn = new MenuItem({ - title: "Collapse", - //label: "Collapse", - icon: icons.join, - execEvent: "", - css: "color:white;", - class: "summarize", - run: () => { - this.collapseToolTip(); - } - }); - } + // createCollapse() { + // this._collapseBtn = new MenuItem({ + // title: "Collapse", + // //label: "Collapse", + // icon: icons.join, + // execEvent: "", + // css: "color:white;", + // class: "summarize", + // run: () => { + // this.collapseToolTip(); + // } + // }); + // } - collapseToolTip() { - if (this._collapseBtn) { - if (this._collapseBtn.spec.title === "Collapse") { - // const newcollapseBtn = new MenuItem({ - // title: "Expand", - // icon: icons.join, - // execEvent: "", - // css: "color:white;", - // class: "summarize", - // run: (state, dispatch, view) => { - // this.collapseToolTip(); - // } - // }); - // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); - // this._collapseBtn = newcollapseBtn; - this.tooltip.style.width = "30px"; - this._collapseBtn.spec.title = "Expand"; - this._collapseBtn.render(this.view); - } - else { - this._collapseBtn.spec.title = "Collapse"; - this.tooltip.style.width = "550px"; - this._collapseBtn.render(this.view); - } - } - } + // collapseToolTip() { + // if (this._collapseBtn) { + // if (this._collapseBtn.spec.title === "Collapse") { + // // const newcollapseBtn = new MenuItem({ + // // title: "Expand", + // // icon: icons.join, + // // execEvent: "", + // // css: "color:white;", + // // class: "summarize", + // // run: (state, dispatch, view) => { + // // this.collapseToolTip(); + // // } + // // }); + // // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); + // // this._collapseBtn = newcollapseBtn; + // this.tooltip.style.width = "30px"; + // this._collapseBtn.spec.title = "Expand"; + // this._collapseBtn.render(this.view); + // } + // else { + // this._collapseBtn.spec.title = "Collapse"; + // this.tooltip.style.width = "550px"; + // this._collapseBtn.render(this.view); + // } + // } + // } createLink() { let markType = schema.marks.link; @@ -913,32 +1036,41 @@ export class TooltipTextMenu { update_mark_doms() { this.reset_mark_doms(); let foundlink = false; - let children = this.extras.childNodes; + // let children = this.extras.childNodes; this._activeMarks.forEach((mark) => { if (this._marksToDoms.has(mark)) { let dom = this._marksToDoms.get(mark); if (dom) dom.style.color = "greenyellow"; } - if (children.length > 1) { - foundlink = true; - } - if (mark.type.name === "link" && children.length === 1) { - // let del = document.createElement("button"); - // del.textContent = "X"; - // del.style.color = "red"; - // del.style.height = "10px"; - // del.style.width = "10px"; - // del.style.marginLeft = "5px"; - // del.onclick = this.deleteLink; - // this.extras.appendChild(del); - let del = this.deleteLinkItem().render(this.view).dom; - this.extras.appendChild(del); - foundlink = true; - } + // if (children.length > 1) { + // foundlink = true; + // } + // if (mark.type.name === "link" && children.length === 1) { + // // let del = document.createElement("button"); + // // del.textContent = "X"; + // // del.style.color = "red"; + // // del.style.height = "10px"; + // // del.style.width = "10px"; + // // del.style.marginLeft = "5px"; + // // del.onclick = this.deleteLink; + // // this.extras.appendChild(del); + // let del = this.deleteLinkItem().render(this.view).dom; + // this.extras.appendChild(del); + // foundlink = true; + // } }); - if (!foundlink) { - if (children.length > 1) { - this.extras.removeChild(children[1]); + // if (!foundlink) { + // if (children.length > 1) { + // this.extras.removeChild(children[1]); + // } + // } + + // keeps brush tool highlighted if active when switching between textboxes + if (!TooltipTextMenuManager.Instance._brushIsEmpty) { + if (this._brushdom) { + const newbrush = this.createBrush(true).render(this.view).dom; + this.tooltip.replaceChild(newbrush, this._brushdom); + this._brushdom = newbrush; } } @@ -1022,3 +1154,53 @@ export class TooltipTextMenu { this.wrapper.remove(); } } + + +class TooltipTextMenuManager { + private static _instance: TooltipTextMenuManager; + + public pinnedX: number = 0; + public pinnedY: number = 0; + public unpinnedX: number = 0; + public unpinnedY: number = 0; + private _isPinned: boolean = false; + + public _brushMarks: Set | undefined; + public _brushIsEmpty: boolean = true; + + public activeMenu: TooltipTextMenu | undefined; + + static get Instance() { + if (!TooltipTextMenuManager._instance) { + TooltipTextMenuManager._instance = new TooltipTextMenuManager(); + } + return TooltipTextMenuManager._instance; + } + + // private pinnedToUnpinned() { + // let position = MainOverlayTextBox.Instance.position; + + // this.unpinnedX = this.pinnedX - position[0]; + // this.unpinnedY = this.pinnedY - position[1]; + // } + + // private unpinnedToPinned() { + // let position = MainOverlayTextBox.Instance.position; + + // this.pinnedX = position[0] + this.unpinnedX; + // this.pinnedY = position[1] + this.unpinnedY; + // } + + public get isPinned() { + return this._isPinned; + } + + public toggleIsPinned() { + // if (this._isPinned) { + // this.pinnedToUnpinned(); + // } else { + // this.unpinnedToPinned(); + // } + this._isPinned = !this._isPinned; + } +} diff --git a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud deleted file mode 100644 index f71886d8c..000000000 Binary files a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud and /dev/null differ diff --git a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud deleted file mode 100644 index 30ddb3091..000000000 Binary files a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud and /dev/null differ -- cgit v1.2.3-70-g09d2 From dbf41b6eaaebe16567289ba4814316b2c79e0b65 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 10:50:53 -0400 Subject: richtexteditor menuicon styling --- src/client/util/TooltipTextMenu.scss | 132 ------------- src/client/util/TooltipTextMenu.tsx | 272 ++++++++++++++------------- src/client/views/nodes/FormattedTextBox.scss | 22 +-- 3 files changed, 153 insertions(+), 273 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 6bafdbbfd..11c937471 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -55,12 +55,6 @@ } } -// .ProseMirror-menu-submenu { -// display: none; -// min-width: 4em; -// left: 100%; -// top: -3px; -// } .ProseMirror-menu-submenu-label:after { content: ""; @@ -72,34 +66,6 @@ right: 4px; top: calc(50% - 4px); } - - - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-disabled { -// opacity: .3; -// } - - -// .ProseMirror-menubar { -// border-top-left-radius: inherit; -// border-top-right-radius: inherit; -// position: relative; -// min-height: 1em; -// color: white; -// padding: 10px 10px; -// top: 0; left: 0; right: 0; -// border-bottom: 1px solid silver; -// background:$dark-color; -// z-index: 10; -// -moz-box-sizing: border-box; -// box-sizing: border-box; -// overflow: visible; -// } .ProseMirror-icon { display: inline-block; @@ -121,74 +87,6 @@ vertical-align: text-top; } } - -// .ProseMirror ul, .ProseMirror ol { -// padding-left: 30px; -// } - -// .ProseMirror blockquote { -// padding-left: 1em; -// border-left: 3px solid #eee; -// margin-left: 0; margin-right: 0; -// } - -// .ProseMirror-example-setup-style img { -// cursor: default; -// } - -// .ProseMirror-prompt { -// background: white; -// padding: 5px 10px 5px 15px; -// border: 1px solid silver; -// position: fixed; -// border-radius: 3px; -// z-index: 11; -// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); -// } - -// .ProseMirror-prompt h5 { -// margin: 0; -// font-weight: normal; -// font-size: 100%; -// color: #444; -// } - -// .ProseMirror-prompt input[type="text"], -// .ProseMirror-prompt textarea { -// background: white; -// border: none; -// outline: none; -// } - -// .ProseMirror-prompt input[type="text"] { -// padding: 0 4px; -// } - -// .ProseMirror-prompt-close { -// position: absolute; -// left: 2px; top: 1px; -// color: #666; -// border: none; background: transparent; padding: 0; -// } - -// .ProseMirror-prompt-close:after { -// content: "✕"; -// font-size: 12px; -// } - -// .ProseMirror-invalid { -// background: #ffc; -// border: 1px solid #cc7; -// border-radius: 4px; -// padding: 5px 10px; -// position: absolute; -// min-width: 10em; -// } - -// .ProseMirror-prompt-buttons { -// margin-top: 5px; -// display: none; -// } .wrapper { position: absolute; @@ -224,36 +122,6 @@ } } -// .tooltipExtras { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-115px); -// pointer-events: all; -// height: 25px; -// width:fit-content; -// .ProseMirror-example-setup-style hr { -// padding: 2px 10px; -// border: none; -// margin: 1em 0; -// } - -// .ProseMirror-example-setup-style hr:after { -// content: ""; -// display: block; -// height: 1px; -// background-color: silver; -// line-height: 2px; -// } -// } - #link-drag { background-color: #323232; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 6070d2ef4..d0bb753ba 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -154,10 +154,10 @@ export class TooltipTextMenu { // link menu this.updateLinkMenu(); - let dropdown = await this.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(this.view).dom; - this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); - this._linkDropdownDom = newLinkDropdowndom; + // let dropdown = await this.createLinkDropdown(); + // let newLinkDropdowndom = dropdown.render(this.view).dom; + // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + // this._linkDropdownDom = newLinkDropdowndom; // list of font styles this.initFontStyles(); @@ -302,13 +302,24 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "13px"; - this.linkDrag.style.height = "13px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.id = "link-drag"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { + this.linkEditor.style.color = "black"; + this.linkText = document.createElement("div"); + this.linkText.style.cssFloat = "left"; + this.linkText.style.marginRight = "5px"; + this.linkText.style.marginLeft = "5px"; + this.linkText.setAttribute("contenteditable", "true"); + this.linkText.style.whiteSpace = "nowrap"; + this.linkText.style.width = "150px"; + this.linkText.style.overflow = "hidden"; + this.linkText.style.color = "white"; + this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; + let linkBtn = document.createElement("div"); + linkBtn.textContent = ">>"; + linkBtn.style.width = "10px"; + linkBtn.style.height = "10px"; + linkBtn.style.color = "white"; + linkBtn.style.cssFloat = "left"; + linkBtn.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); if (link) { @@ -329,136 +340,137 @@ export class TooltipTextMenu { e.preventDefault(); } }; + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "15px"; + this.linkDrag.style.height = "15px"; + this.linkDrag.title = "Drag to create link"; + this.linkDrag.style.color = "black"; + this.linkDrag.style.background = "black"; + this.linkDrag.style.cssFloat = "left"; + this.linkDrag.onpointerdown = (e: PointerEvent) => { + if (!this.editorProps) return; + let dragData = new DragManager.LinkDragData(this.editorProps.Document); + dragData.dontClearTextBox = true; + // hack to get source context -sy + let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); + e.stopPropagation(); + let ctrlKey = e.ctrlKey; + DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, + { + handlers: { + dragComplete: action(() => { + if (dragData.linkDocument) { + let linkDoc = dragData.linkDocument; + let proto = Doc.GetProto(linkDoc); + if (proto && docView) { + proto.sourceContext = docView.props.ContainingCollectionDoc; + } + let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { + proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link + } + } + }), + }, + hideSource: false + }); + e.stopPropagation(); + e.preventDefault(); + }; this.linkEditor.appendChild(this.linkDrag); this.tooltip.appendChild(this.linkEditor); } - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - // this.linkText.onkeydown = (e: KeyboardEvent) => { - // if (e.key === "Enter") { - // // this.makeLink(this.linkText!.textContent!); - // e.stopPropagation(); - // e.preventDefault(); - // } - // }; - // // this.tooltip.appendChild(this.linkEditor); - } - - async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - let href = link.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - let linkDoc = await DocServer.GetRefField(linkclicked); - if (linkDoc instanceof Doc) { - let anchor1 = await Cast(linkDoc.anchor1, Doc); - let anchor2 = await Cast(linkDoc.anchor2, Doc); - let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - if (currentDoc && anchor1 && anchor2) { - if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); - if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); - } - } - } - } else { - return href; - } - } - } - } - - async createLinkDropdown() { - let targetTitle = await this.getTextLinkTargetTitle(); - let input = document.createElement("input"); + this.linkText.textContent = link ? link.attrs.href : "-empty-"; - // menu item for input for hyperlink url - // TODO: integrate search to allow users to search for a doc to link to - let linkInfo = new MenuItem({ - title: "", - execEvent: "", - class: "button-setting-disabled", - css: "", - render() { - let p = document.createElement("p"); - p.textContent = "Linked to:"; - - input.type = "text"; - input.placeholder = "Enter URL"; - if (targetTitle) input.value = targetTitle; - input.onclick = (e: MouseEvent) => { - input.select(); - input.focus(); - }; - - let div = document.createElement("div"); - div.appendChild(p); - div.appendChild(input); - return div; - }, - enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } - }); - - // menu item to update/apply the hyperlink to the selected text - let linkApply = new MenuItem({ - title: "", - execEvent: "", - class: "", - css: "", - render() { - let button = document.createElement("button"); - button.className = "link-url-button"; - button.textContent = "Apply hyperlink"; - return button; - }, - enable() { return false; }, - run: (state, dispatch, view, event) => { - event.stopPropagation(); - this.makeLink(input.value, "onRight"); - } - }); - - // menu item to remove the link - // TODO: allow this to be undoable - let self = this; - let deleteLink = new MenuItem({ - title: "Delete link", - execEvent: "", - class: "separated-button", - css: "", - render() { - let button = document.createElement("button"); - button.textContent = "Remove link"; - - let wrapper = document.createElement("div"); - wrapper.appendChild(button); - return wrapper; - }, - enable() { return true; }, - async run() { - self.deleteLink(); - // update link dropdown - let dropdown = await self.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(self.view).dom; - self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); - self._linkDropdownDom = newLinkDropdowndom; + this.linkText.onkeydown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + // this.makeLink(this.linkText!.textContent!); + e.stopPropagation(); + e.preventDefault(); } - }); + }; + // this.tooltip.appendChild(this.linkEditor); + } + // updateLinkMenu() { + // if (!this.linkEditor || !this.linkText) { + // this.linkEditor = document.createElement("div"); + // this.linkEditor.className = "ProseMirror-icon menuicon"; + // this.linkDrag = document.createElement("img"); + // this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + // this.linkDrag.style.width = "13px"; + // this.linkDrag.style.height = "13px"; + // this.linkDrag.title = "Drag to create link"; + // this.linkDrag.id = "link-drag"; + // this.linkDrag.onpointerdown = (e: PointerEvent) => { + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // if (link) { + // let href: string = link.attrs.href; + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // let docid = href.replace(Utils.prepend("/doc/"), ""); + // DocServer.GetRefField(docid).then(action((f: Opt) => { + // if (f instanceof Doc) { + // if (DocumentManager.Instance.getDocumentView(f)) { + // DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); + // } + // else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); + // } + // })); + // } + // // TODO This should have an else to handle external links + // e.stopPropagation(); + // e.preventDefault(); + // } + // }; + // this.linkEditor.appendChild(this.linkDrag); + // this.tooltip.appendChild(this.linkEditor); + // } - let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; - return linkDropdown; - } + // // let node = this.view.state.selection.$from.nodeAfter; + // // let link = node && node.marks.find(m => m.type.name === "link"); + // // this.linkText.textContent = link ? link.attrs.href : "-empty-"; + + // // this.linkText.onkeydown = (e: KeyboardEvent) => { + // // if (e.key === "Enter") { + // // // this.makeLink(this.linkText!.textContent!); + // // e.stopPropagation(); + // // e.preventDefault(); + // // } + // // }; + // // // this.tooltip.appendChild(this.linkEditor); + // } + // async getTextLinkTargetTitle() { + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // if (link) { + // let href = link.attrs.href; + // if (href) { + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + // if (linkclicked) { + // let linkDoc = await DocServer.GetRefField(linkclicked); + // if (linkDoc instanceof Doc) { + // let anchor1 = await Cast(linkDoc.anchor1, Doc); + // let anchor2 = await Cast(linkDoc.anchor2, Doc); + // let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + // if (currentDoc && anchor1 && anchor2) { + // if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); + // if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); + // } + // } + // } + // } else { + // return href; + // } + // } + // } + // } dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 29e8b14a8..f92ccf9f5 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -46,17 +46,17 @@ height: 100%; } -.menuicon { - display: inline-block; - border-right: 1px solid rgba(0, 0, 0, 0.2); - color: #888; - line-height: 1; - padding: 0 7px; - margin: 1px; - cursor: pointer; - text-align: center; - min-width: 1.4em; -} +// .menuicon { +// display: inline-block; +// border-right: 1px solid rgba(0, 0, 0, 0.2); +// color: #888; +// line-height: 1; +// padding: 0 7px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 1.4em; +// } .strong, .heading { -- cgit v1.2.3-70-g09d2 From 3a5e787e3a3c42ec3208f9f75bd8bfbac71aeb81 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 11:44:11 -0400 Subject: added dropdowns to link tool and brush tool --- src/client/util/TooltipTextMenu.tsx | 315 +++++++++++++++++++++++++----------- 1 file changed, 221 insertions(+), 94 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index d0bb753ba..ce0237527 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -50,6 +50,7 @@ export class TooltipTextMenu { private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; + private _brushDropdownDom?: Node; private fontSizeDom?: Node; private fontStyleDom?: Node; @@ -154,10 +155,9 @@ export class TooltipTextMenu { // link menu this.updateLinkMenu(); - // let dropdown = await this.createLinkDropdown(); - // let newLinkDropdowndom = dropdown.render(this.view).dom; - // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); - // this._linkDropdownDom = newLinkDropdowndom; + let dropdown = await this.createLinkDropdown(); + this._linkDropdownDom = dropdown.render(this.view).dom; + this.tooltip.appendChild(this._linkDropdownDom); // list of font styles this.initFontStyles(); @@ -171,18 +171,18 @@ export class TooltipTextMenu { // init brush tool this._brushdom = this.createBrush().render(this.view).dom; this.tooltip.appendChild(this._brushdom); - // this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; - // this.tooltip.appendChild(this._brushDropdownDom); + this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; + this.tooltip.appendChild(this._brushDropdownDom); - // star and pin - this.tooltip.appendChild(this.createLink().render(this.view).dom); + // star + // this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); // this.updateListItemDropdown(":", this.listTypeBtnDom); // - this.updateFromDash(view, undefined, undefined); + await this.updateFromDash(view, undefined, undefined); // TooltipTextMenu.Toolbar = this.wrapper; // dragger @@ -302,11 +302,11 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkEditor.style.color = "black"; + // this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); - this.linkText.style.cssFloat = "left"; - this.linkText.style.marginRight = "5px"; - this.linkText.style.marginLeft = "5px"; + // this.linkText.style.cssFloat = "left"; + // this.linkText.style.marginRight = "5px"; + // this.linkText.style.marginLeft = "5px"; this.linkText.setAttribute("contenteditable", "true"); this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; @@ -345,9 +345,10 @@ export class TooltipTextMenu { this.linkDrag.style.width = "15px"; this.linkDrag.style.height = "15px"; this.linkDrag.title = "Drag to create link"; - this.linkDrag.style.color = "black"; - this.linkDrag.style.background = "black"; - this.linkDrag.style.cssFloat = "left"; + this.linkDrag.id = "link-drag"; + // this.linkDrag.style.color = "black"; + // this.linkDrag.style.background = "black"; + // this.linkDrag.style.cssFloat = "left"; this.linkDrag.onpointerdown = (e: PointerEvent) => { if (!this.editorProps) return; let dragData = new DragManager.LinkDragData(this.editorProps.Document); @@ -366,7 +367,7 @@ export class TooltipTextMenu { if (proto && docView) { proto.sourceContext = docView.props.ContainingCollectionDoc; } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + let text = this.makeLinkToDoc(linkDoc, ctrlKey ? "onRight" : "inTab"); if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link } @@ -396,81 +397,125 @@ export class TooltipTextMenu { // this.tooltip.appendChild(this.linkEditor); } - // updateLinkMenu() { - // if (!this.linkEditor || !this.linkText) { - // this.linkEditor = document.createElement("div"); - // this.linkEditor.className = "ProseMirror-icon menuicon"; - // this.linkDrag = document.createElement("img"); - // this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - // this.linkDrag.style.width = "13px"; - // this.linkDrag.style.height = "13px"; - // this.linkDrag.title = "Drag to create link"; - // this.linkDrag.id = "link-drag"; - // this.linkDrag.onpointerdown = (e: PointerEvent) => { - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // if (link) { - // let href: string = link.attrs.href; - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // let docid = href.replace(Utils.prepend("/doc/"), ""); - // DocServer.GetRefField(docid).then(action((f: Opt) => { - // if (f instanceof Doc) { - // if (DocumentManager.Instance.getDocumentView(f)) { - // DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); - // } - // else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); - // } - // })); - // } - // // TODO This should have an else to handle external links - // e.stopPropagation(); - // e.preventDefault(); - // } - // }; - // this.linkEditor.appendChild(this.linkDrag); - // this.tooltip.appendChild(this.linkEditor); - // } + async getTextLinkTargetTitle() { + let node = this.view.state.selection.$from.nodeAfter; + let link = node && node.marks.find(m => m.type.name === "link"); + // let href = link!.attrs.href; + if (link) { + let href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + let linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + let anchor1 = await Cast(linkDoc.anchor1, Doc); + let anchor2 = await Cast(linkDoc.anchor2, Doc); + let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) { + return StrCast(anchor2.title); + } + if (Doc.AreProtosEqual(currentDoc, anchor2)) { + return StrCast(anchor1.title); + } + } + } + } + } else { + return href; + } + } else { + return link.attrs.title; + } + } + } - // // let node = this.view.state.selection.$from.nodeAfter; - // // let link = node && node.marks.find(m => m.type.name === "link"); - // // this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - // // this.linkText.onkeydown = (e: KeyboardEvent) => { - // // if (e.key === "Enter") { - // // // this.makeLink(this.linkText!.textContent!); - // // e.stopPropagation(); - // // e.preventDefault(); - // // } - // // }; - // // // this.tooltip.appendChild(this.linkEditor); - // } + async createLinkDropdown() { + let targetTitle = await this.getTextLinkTargetTitle(); + let input = document.createElement("input"); + + // menu item for input for hyperlink url + // TODO: integrate search to allow users to search for a doc to link to + let linkInfo = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Linked to:"; + + input.type = "text"; + input.placeholder = "Enter URL"; + console.log(targetTitle); + if (targetTitle) input.value = targetTitle; + input.onclick = (e: MouseEvent) => { + input.select(); + input.focus(); + }; + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(input); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + // menu item to update/apply the hyperlink to the selected text + let linkApply = new MenuItem({ + title: "", + execEvent: "", + class: "", + css: "", + render() { + let button = document.createElement("button"); + button.className = "link-url-button"; + button.textContent = "Apply hyperlink"; + return button; + }, + enable() { return false; }, + run: (state, dispatch, view, event) => { + event.stopPropagation(); + this.makeLinkToURL(input.value, "onRight"); + } + }); + + // menu item to remove the link + // TODO: allow this to be undoable + let self = this; + let deleteLink = new MenuItem({ + title: "Delete link", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Remove link"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + async run() { + self.deleteLink(); + // update link dropdown + let dropdown = await self.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(self.view).dom; + self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); + self._linkDropdownDom = newLinkDropdowndom; + } + }); - // async getTextLinkTargetTitle() { - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // if (link) { - // let href = link.attrs.href; - // if (href) { - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - // if (linkclicked) { - // let linkDoc = await DocServer.GetRefField(linkclicked); - // if (linkDoc instanceof Doc) { - // let anchor1 = await Cast(linkDoc.anchor1, Doc); - // let anchor2 = await Cast(linkDoc.anchor2, Doc); - // let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - // if (currentDoc && anchor1 && anchor2) { - // if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); - // if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); - // } - // } - // } - // } else { - // return href; - // } - // } - // } - // } + + let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; + return linkDropdown; + } dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; @@ -532,7 +577,7 @@ export class TooltipTextMenu { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } - makeLink = (targetDoc: Doc, location: string): string => { + makeLinkToDoc = (targetDoc: Doc, location: string): string => { let target = Utils.prepend("/doc/" + targetDoc[Id]); let node = this.view.state.selection.$from.nodeAfter; let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] }); @@ -548,6 +593,15 @@ export class TooltipTextMenu { return ""; } + makeLinkToURL = (target: String, lcoation: string) => { + let node = this.view.state.selection.$from.nodeAfter; + let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); + this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + node = this.view.state.selection.$from.nodeAfter; + link = node && node.marks.find(m => m.type.name === "link"); + } + deleteLink = () => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); @@ -711,7 +765,7 @@ export class TooltipTextMenu { label: "Summarize", icon: icons.join, css: "color:white;", - class: "summarize", + class: "menuicon", execEvent: "", run: (state, dispatch) => { TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); @@ -743,15 +797,21 @@ export class TooltipTextMenu { height: 32, width: 32, path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z" }; + let self = this; return new MenuItem({ title: "Brush tool", label: "Brush tool", icon: icon, css: "color:white;", - class: active ? "brush-active" : "brush", + class: active ? "menuicon-active" : "menuicon", execEvent: "", run: (state, dispatch) => { this.brush_function(state, dispatch); + + // update dropdown with marks + let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom; + self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); + self._brushDropdownDom = newBrushDropdowndom; }, active: (state) => { return true; @@ -798,6 +858,67 @@ export class TooltipTextMenu { } + createBrushDropdown(active: boolean = false) { + let label = "Stored marks: "; + if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) { + TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => { + const markType = mark.type; + label += markType.name; + label += ", "; + }); + label = label.substring(0, label.length - 2); + } else { + label = "No marks are currently stored"; + } + + + let brushInfo = new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "button-setting-disabled", + css: "", + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let self = this; + let clearBrush = new MenuItem({ + title: "Clear brush", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Clear brush"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + run() { + TooltipTextMenuManager.Instance._brushIsEmpty = true; + TooltipTextMenuManager.Instance._brushMarks = new Set(); + + // update brush tool + // TODO: this probably isn't very clean + let newBrushdom = self.createBrush().render(self.view).dom; + self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom); + self._brushdom = newBrushdom; + let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom; + self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); + self._brushDropdownDom = newBrushDropdowndom; + } + }); + + let hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0; + let brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; + return brushDom; + } + // createCollapse() { // this._collapseBtn = new MenuItem({ // title: "Collapse", @@ -978,7 +1099,7 @@ export class TooltipTextMenu { update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); } //updates the tooltip menu when the selection changes - public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { + public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { if (!view) { console.log("no editor? why?"); return; @@ -1000,6 +1121,12 @@ export class TooltipTextMenu { } //UPDATE LIST ITEM DROPDOWN + // update link dropdown + let dropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(this.view).dom; + this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + this._linkDropdownDom = newLinkDropdowndom; + //UPDATE FONT STYLE DROPDOWN let activeStyles = this.activeMarksOnSelection(this.fontStyles); if (activeStyles !== undefined) { -- cgit v1.2.3-70-g09d2 From 1b0f0e5e434bc23128b50303ffc2b13244ba6325 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 12:01:35 -0400 Subject: started colors on rich text editor --- src/client/util/TooltipTextMenu.tsx | 100 ++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 10 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index ce0237527..9dd8e6688 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -21,6 +21,7 @@ import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; +import { PastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -623,16 +624,6 @@ export class TooltipTextMenu { } - public static insertStar(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - let mark = state.schema.marks.highlight.create(); - let tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - let content = tr.selection.content(); - let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); - dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); - return true; - } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { @@ -774,6 +765,93 @@ export class TooltipTextMenu { }); } + public static insertStar(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + + createColorTool() { + return new MenuItem({ + title: "Summarize", + label: "Summarize", + icon: icons.join, + css: "color:white;", + class: "menuicon", + execEvent: "", + run: (state, dispatch) => { + TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); + } + + }); + } + + insertColor(color: String, state: EditorState, dispatch: any) { + + } + + createColorDropdown() { + // menu item for color picker + let colors = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Change color:"; + + // input.type = "text"; + // input.placeholder = "Enter URL"; + // console.log(targetTitle); + // if (targetTitle) input.value = targetTitle; + // input.onclick = (e: MouseEvent) => { + // input.select(); + // input.focus(); + // }; + + let colorsWrapper = document.createElement("div"); + colorsWrapper.className = "colorPicker-wrapper"; + + let colors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + "#f1efeb", + "#000" + ]; + + colors.forEach(color => { + let button = document.createElement("button"); + button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; + if (color) button.style.backgroundColor = color; + + }); + + let div = document.createElement("div"); + div.appendChild(p); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; + return colorDropdown; + } + deleteLinkItem() { const icon = { height: 16, width: 16, @@ -1307,6 +1385,8 @@ class TooltipTextMenuManager { public _brushMarks: Set | undefined; public _brushIsEmpty: boolean = true; + public color: String = "#000"; + public activeMenu: TooltipTextMenu | undefined; static get Instance() { -- cgit v1.2.3-70-g09d2 From e8095539c208169b1883e8e73686382d8d8c6336 Mon Sep 17 00:00:00 2001 From: Fawn Date: Sat, 12 Oct 2019 17:18:58 -0400 Subject: created color picker for text on rich text menu --- src/client/util/RichTextSchema.tsx | 18 +++++++++ src/client/util/TooltipTextMenu.scss | 21 +++++++++++ src/client/util/TooltipTextMenu.tsx | 73 ++++++++++++++---------------------- 3 files changed, 67 insertions(+), 45 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5502577b..e943b71e8 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -308,6 +308,24 @@ export const marks: { [index: string]: MarkSpec } = { } }, + // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. + color: { + attrs: { + color: { default: "#000" } + }, + inclusive: false, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { color: dom.getAttribute("color") }; + } + }], + toDOM(node: any) { + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; + // ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + // ["a", { ...node.attrs }, 0]; + } + }, + // :: MarkSpec An emphasis mark. Rendered as an `` element. // Has parse rules that also match `` and `font-style: italic`. em: { diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 11c937471..f94799936 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -336,6 +336,27 @@ } } +.colorPicker-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 3px; + margin-left: -3px; + width: calc(100% + 6px); +} + +button.colorPicker { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: none !important; + + &.active { + border: 2px solid white !important; + } +} + // @import "../views/globalCssVariables"; // .ProseMirror-textblock-dropdown { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9dd8e6688..32a910497 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -28,9 +28,11 @@ const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { + public static Toolbar: HTMLDivElement | undefined; + // editor state private view: EditorView; - private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + // private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[] = []; private fontSizes: MarkType[] = []; private listTypes: (NodeType | any)[] = []; @@ -39,40 +41,38 @@ export class TooltipTextMenu { private listTypeToIcon: Map = new Map(); private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); - // private _brushMarks?: Set; + private _collapsed: boolean = false; //private link: HTMLAnchorElement; // editor doms public tooltip: HTMLElement = document.createElement("div"); private wrapper: HTMLDivElement = document.createElement("div"); + // editor button doms private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; private _brushDropdownDom?: Node; - private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private _collapsed: boolean = false; + // private _collapseBtn?: MenuItem; // private _brushIsEmpty: boolean = true; - public static Toolbar: HTMLDivElement | undefined; - constructor(view: EditorView) { this.view = view; - // replace old active menu with this - if (TooltipTextMenuManager.Instance.activeMenu) { - TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); - } - TooltipTextMenuManager.Instance.activeMenu = this; + // // replace old active menu with this + // if (TooltipTextMenuManager.Instance.activeMenu) { + // TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); + // } + // TooltipTextMenuManager.Instance.activeMenu = this; // initialize the tooltip this.createTooltip(view); @@ -84,30 +84,10 @@ export class TooltipTextMenu { // positioning? TooltipTextMenu.Toolbar = this.wrapper; - - // // position wrapper - // if (TooltipTextMenuManager.Instance.isPinned) { - // this.wrapper.style.position = "absolute"; - // this.wrapper.style.left = TooltipTextMenuManager.Instance.pinnedX + "px"; - // this.wrapper.style.top = TooltipTextMenuManager.Instance.pinnedY + "px"; - - // // if pinned, append to mainview - // const mainView = document.querySelector("#main-div"); - // mainView && mainView.appendChild(this.wrapper); - // } else { - // this.wrapper.style.position = "absolute"; - // this.wrapper.style.top = (this.wrapper.offsetTop + TooltipTextMenuManager.Instance.unpinnedY) + "px"; - // this.wrapper.style.left = (this.wrapper.offsetLeft + TooltipTextMenuManager.Instance.unpinnedX) + "px"; - - // // add tooltip to outerdiv to circumvent scaling problem - // const outer_div = this.editorProps.outer_div; - // outer_div && outer_div(this.wrapper); - // } - } private async createTooltip(view: EditorView) { - // initialize element + // initialize tooltip dom this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; @@ -154,6 +134,10 @@ export class TooltipTextMenu { }); + // color menu + this.tooltip.appendChild(this.createColorTool().render(this.view).dom); + this.tooltip.appendChild(this.createColorDropdown().render(this.view).dom); + // link menu this.updateLinkMenu(); let dropdown = await this.createLinkDropdown(); @@ -785,18 +769,22 @@ export class TooltipTextMenu { class: "menuicon", execEvent: "", run: (state, dispatch) => { - TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); + TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } }); } - insertColor(color: String, state: EditorState, dispatch: any) { + public static insertColor(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let colorMark = state.schema.mark(state.schema.marks.color, { color: color }); + dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark)); } createColorDropdown() { // menu item for color picker + let self = this; let colors = new MenuItem({ title: "", execEvent: "", @@ -806,15 +794,6 @@ export class TooltipTextMenu { let p = document.createElement("p"); p.textContent = "Change color:"; - // input.type = "text"; - // input.placeholder = "Enter URL"; - // console.log(targetTitle); - // if (targetTitle) input.value = targetTitle; - // input.onclick = (e: MouseEvent) => { - // input.select(); - // input.focus(); - // }; - let colorsWrapper = document.createElement("div"); colorsWrapper.className = "colorPicker-wrapper"; @@ -834,12 +813,16 @@ export class TooltipTextMenu { colors.forEach(color => { let button = document.createElement("button"); button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; - if (color) button.style.backgroundColor = color; - + if (color) { + button.style.backgroundColor = color; + button.onclick = e => TooltipTextMenuManager.Instance.color = color; + } + colorsWrapper.appendChild(button); }); let div = document.createElement("div"); div.appendChild(p); + div.appendChild(colorsWrapper); return div; }, enable() { return false; }, -- cgit v1.2.3-70-g09d2 From bd796f24677b2aacd32a371da6d2b7c7eb354018 Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Sun, 13 Oct 2019 15:48:38 -0400 Subject: pinching code improved --- src/client/util/InteractionUtils.ts | 14 ++++++----- .../collectionFreeForm/CollectionFreeFormView.tsx | 29 ++++++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts index 63cba4982..e58635a6f 100644 --- a/src/client/util/InteractionUtils.ts +++ b/src/client/util/InteractionUtils.ts @@ -28,13 +28,15 @@ export namespace InteractionUtils { * @param oldPoint2 - previous point 2 */ export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number { - const leniency = 10; - let dist1 = TwoPointEuclidist(oldPoint1, pt1) + leniency; - let dist2 = TwoPointEuclidist(oldPoint2, pt2) + leniency; + let threshold = 4; + let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); + let newDist = TwoPointEuclidist(pt1, pt2); - if (Math.sign(dist1) === Math.sign(dist2)) { - let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2); - let newDist = TwoPointEuclidist(pt1, pt2); + /** if they have the same sign, then we are either pinching in or out. + * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch) + * so that it can still pan without freaking out + */ + if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) { return Math.sign(oldDist - newDist); } return 0; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 54d5b95b6..915126742 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -289,7 +289,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - pan = (e: PointerEvent | React.Touch): void => { + pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { // I think it makes sense for the marquee menu to go away when panned. -syip2 MarqueeOptionsMenu.Instance.fadeOut(true); @@ -329,8 +329,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2; } this.setPan(x - dx, y - dy); - this._lastX = e.pageX; - this._lastY = e.pageY; + this._lastX = e.clientX; + this._lastY = e.clientY; } @action @@ -386,11 +386,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; - let delta = dir * (d1 + d2); - this.zoom(centerX, centerY, delta, 250); + + // calculate the raw delta value + let rawDelta = (dir * (d1 + d2)); + + // this floors and ceils the delta value to prevent jitteriness + let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 16); + this.zoom(centerX, centerY, delta); this.prevPoints.set(pt1.identifier, pt1); this.prevPoints.set(pt2.identifier, pt2); } + // this is not zooming. derive some form of panning from it. + else { + // use the centerx and centery as the "new mouse position" + let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; + this.pan({ clientX: centerX, clientY: centerY }); + this._lastX = centerX; + this._lastY = centerY; + } } } } @@ -406,7 +420,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action - zoom = (pointX: number, pointY: number, deltaY: number, coefficient: number): void => { + zoom = (pointX: number, pointY: number, deltaY: number): void => { + console.log(deltaY); let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); @@ -428,7 +443,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } else if (this.props.active()) { e.stopPropagation(); - this.zoom(e.clientX, e.clientY, e.deltaY, 1) + this.zoom(e.clientX, e.clientY, e.deltaY) } } -- cgit v1.2.3-70-g09d2 From f72919c41ea4e918b315dd11cbae993518181b5b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 14 Oct 2019 14:53:02 -0400 Subject: fixed shift dragging icons --- src/client/util/DragManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 2c316ccdf..169f2edec 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -430,12 +430,13 @@ export namespace DragManager { } if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); + finishDrag && finishDrag(dragData); CollectionDockingView.Instance.StartOtherDrag({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 - }, docs); + }, dragData.droppedDocuments); } //TODO: Why can't we use e.movementX and e.movementY? let moveX = e.pageX - lastX; -- cgit v1.2.3-70-g09d2 From 33811c112c7e479813908ba10f72813954a3e289 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 15 Oct 2019 16:25:10 -0400 Subject: working version of inking buttons --- src/client/documents/Documents.ts | 2 ++ src/client/util/DragManager.ts | 4 +-- src/client/util/SelectionManager.ts | 11 -------- src/client/views/CollectionLinearView.tsx | 4 +-- src/client/views/InkingControl.tsx | 6 ++-- src/client/views/MainView.tsx | 6 ++-- src/client/views/nodes/ColorBox.tsx | 33 ++++++++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 7 +++-- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 16 ++++++----- src/new_fields/Doc.ts | 4 +++ .../authentication/models/current_user_utils.ts | 31 +++++++++++--------- 12 files changed, 78 insertions(+), 48 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6df172f46..f4fce34ac 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -93,6 +93,8 @@ export interface DocumentOptions { autoHeight?: boolean; removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document dbDoc?: Doc; + unchecked?: ScriptField; // returns whether a check box is unchecked + activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop icon?: string; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 169f2edec..92666c03c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -234,9 +234,9 @@ export namespace DragManager { export let StartDragFunctions: (() => void)[] = []; - export async function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { runInAction(() => StartDragFunctions.map(func => func())); - await dragData.draggedDocuments.map(d => d.dragFactory); + dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { (dropData.droppedDocuments = diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index df1b46b33..2d717ca57 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -45,17 +45,6 @@ export namespace SelectionManager { } const manager = new Manager(); - reaction(() => manager.SelectedDocuments, sel => { - let targetColor = "#FFFFFF"; - if (sel.length > 0) { - let firstView = sel[0]; - let doc = firstView.props.Document; - let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); - let stored = StrCast(targetDoc.backgroundColor); - stored.length > 0 && (targetColor = stored); - } - InkingControl.Instance.updateSelectedColor(targetColor); - }, { fireImmediately: true }); export function DeselectDoc(docView: DocumentView): void { manager.DeselectDoc(docView); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 9d1dd7749..eb3c768d0 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { drop = action((e: Event, de: DragManager.DropEvent) => { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; - if (!doc.onDragStart && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { + if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" }); dbox.dragFactory = doc; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; @@ -90,7 +90,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { return

(); @computed get docButtons() { - console.log("stuff = " + this.flyoutWidthFunc() + " " + this.getContentsHeight()); if (CurrentUserUtils.UserDocument.expandingButtons instanceof Doc) { - return
+ return
; const ColorDocument = makeInterface(documentSchema); @@ -14,9 +17,35 @@ const ColorDocument = makeInterface(documentSchema); @observer export class ColorBox extends DocStaticComponent(ColorDocument) { public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); } + _selectedDisposer: IReactionDisposer | undefined; + componentDidMount() { + this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(), + action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"), + { fireImmediately: true }); + + // compare to this reaction that used to be in Selection Manager + // reaction(() => manager.SelectedDocuments, sel => { + // let targetColor = "#FFFFFF"; + // if (sel.length > 0) { + // let firstView = sel[0]; + // let doc = firstView.props.Document; + // let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); + // let stored = StrCast(targetDoc.backgroundColor); + // stored.length > 0 && (targetColor = stored); + // } + // InkingControl.Instance.updateSelectedColor(targetColor); + // }, { fireImmediately: true }); + } + componentWillUnmount() { + this._selectedDisposer && this._selectedDisposer(); + } + + @observable _startupColor = "black"; + render() { - return
e.button === 0 && !e.ctrlKey && e.stopPropagation()}> - + return
e.button === 0 && !e.ctrlKey && e.stopPropagation()}> +
; } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c0e5185c1..6f99c541f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -107,7 +107,7 @@ export const documentSchema = createSchema({ removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - dragFactory: Doc, // the document that serves as the "template" for the onDragStart script + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents isTemplate: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed @@ -676,6 +676,7 @@ export class DocumentView extends DocComponent(Docu const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"]; + let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear return (
(Docu transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", color: StrCast(this.Document.color), - outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", - border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, + outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", + border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, background: this.props.Document.type === DocumentType.FONTICON || this.props.Document.viewType === CollectionViewType.Linear ? undefined : backgroundColor, width: animwidth, height: animheight, diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index aa442cd92..efe47d8a8 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -18,7 +18,7 @@ export class FontIconBox extends DocComponent( render() { return ; } } \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 181f37d36..1bdff3ec7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -927,16 +927,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onMouseUp = (e: React.MouseEvent): void => { e.stopPropagation(); - // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there are nested prosemirrors. We only want the lowest level prosemirror to be invoked. - if ((this._editorView as any).mouseDown) { - let originalUpHandler = (this._editorView as any).mouseDown.up; - (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler); - (this._editorView as any).mouseDown.up = (e: MouseEvent) => { + let view = this._editorView as any; + // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there + // are nested prosemirrors. We only want the lowest level prosemirror to be invoked. + if (view.mouseDown) { + let originalUpHandler = view.mouseDown.up; + view.root.removeEventListener("mouseup", originalUpHandler); + view.mouseDown.up = (e: MouseEvent) => { !(e as any).formattedHandled && originalUpHandler(e); - e.stopPropagation(); + // e.stopPropagation(); (e as any).formattedHandled = true; }; - (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up); + view.root.addEventListener("mouseup", view.mouseDown.up); } } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 4bed113e3..276596fb8 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -525,6 +525,7 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { const copy = new Doc(copyProtoId, true); Object.keys(doc).forEach(key => { + let cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); if (key === "proto" && copyProto) { if (doc[key] instanceof Doc) { @@ -533,6 +534,8 @@ export namespace Doc { } else { if (field instanceof RefField) { copy[key] = field; + } else if (cfield instanceof ComputedField) { + copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); } else if (field instanceof ObjectField) { copy[key] = ObjectField.MakeCopy(field); } else if (field instanceof Promise) { @@ -735,5 +738,6 @@ Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.Make Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); Scripting.addGlobal(function aliasDocs(field: any) { return new List(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); +Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2) }); Scripting.addGlobal(function undo() { return UndoManager.Undo(); }); Scripting.addGlobal(function redo() { return UndoManager.Redo(); }); \ No newline at end of file diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index c4b91dadd..3c4a46ed8 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -8,7 +8,7 @@ import { UndoManager } from "../../../client/util/UndoManager"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; import { Cast, PromiseValue } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { RouteStore } from "../../RouteStore"; @@ -45,29 +45,32 @@ export class CurrentUserUtils { } // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static setupCreatorButtons() { - let docProtoData: { title: string, icon: string, drag?: string, click?: string }[] = [ + static setupCreatorButtons(doc: Doc) { + doc.activePen = doc; + let docProtoData: { title: string, icon: string, drag?: string, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [ { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' }, { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' }, { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' }, { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, - { title: "pen", icon: "pen-nib", click: 'activatePen(); setInkWidth(2);' }, - { title: "highlighter", icon: "pen", click: 'activateBrush(); setInkWidth(20);' }, - { title: "eraser", icon: "eraser", click: 'activateEraser();' }, - { title: "none", icon: "pause", click: 'deactivateInk();' }, + { title: "pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "highlighter", icon: "pen", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "none", icon: "pause", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ - nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "alias" : undefined, - title: data.title, icon: data.icon, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined + nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, + unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen, + backgroundColor: data.backgroundColor })); } // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static setupCreatePanel(sidebarContainer: Doc) { + static setupCreatePanel(sidebarContainer: Doc, doc: Doc) { // setup a masonry view of all he creators - const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(), { + const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), { width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons" }); // setup a color picker @@ -129,7 +132,7 @@ export class CurrentUserUtils { doc.sidebarContainer = new Doc(); (doc.sidebarContainer as Doc).chromeStatus = "disabled"; - doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc); + doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc, doc); doc.LibraryBtn = this.setupLibraryPanel(doc.sidebarContainer as Doc, doc); doc.SearchBtn = this.setupSearchPanel(doc.sidebarContainer as Doc); @@ -143,9 +146,9 @@ export class CurrentUserUtils { /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupExpandingButtons(doc: Doc) { doc.undoBtn = Docs.Create.FontIconDocument( - { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); doc.redoBtn = Docs.Create.FontIconDocument( - { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], { title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", -- cgit v1.2.3-70-g09d2 From 6d9cad19047b9970a8429771e7c0e03554e63f39 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 12:13:30 -0400 Subject: more small fixes. dragging dragIcons, pdfmenu highlighting, fonticonbox hover text --- src/client/util/DragManager.ts | 3 +- src/client/views/DocumentDecorations.tsx | 1 + src/client/views/MainView.tsx | 4 ++- src/client/views/collections/CollectionSubView.tsx | 7 ++--- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/PDFBox.scss | 1 + src/client/views/pdf/PDFMenu.scss | 2 +- .../authentication/models/current_user_utils.ts | 32 +++++++++++----------- 8 files changed, 28 insertions(+), 24 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 92666c03c..dcd19e50b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -212,6 +212,7 @@ export namespace DragManager { dropAction: dropActionType; userDropAction: dropActionType; moveDocument?: MoveFunction; + isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; [id: string]: any; } @@ -240,7 +241,7 @@ export namespace DragManager { StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { (dropData.droppedDocuments = - dragData.draggedDocuments.map(d => ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result : + dragData.draggedDocuments.map(d => !dragData.isSelectionMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? ScriptCast(d.onDragStart).script.run({ this: d }).result : dragData.userDropAction === "alias" || (!dragData.userDropAction && dragData.dropAction === "alias") ? Doc.MakeAlias(d) : dragData.userDropAction === "copy" || (!dragData.userDropAction && dragData.dropAction === "copy") ? Doc.MakeCopy(d, true) : d) ); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3d73f048d..755739a11 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -202,6 +202,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top); dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument; + dragData.isSelectionMove = true; this.Interacting = true; this._hidden = true; document.removeEventListener("pointermove", this.onBackgroundMove); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8035916a2..0e15784f7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,7 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, - faMusic, faObjectGroup, faPause, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt + faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; @@ -102,7 +102,9 @@ export class MainView extends React.Component { library.add(faGlobeAsia); library.add(faUndoAlt); library.add(faRedoAlt); + library.add(faMousePointer); library.add(faPen); + library.add(faHighlighter); library.add(faEraser); library.add(faPenNib); library.add(faFilm); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fdbe5339d..9919a9dc3 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -133,11 +133,10 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { if (de.data.dropAction || de.data.userDropAction) { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } else if (de.data.moveDocument) { - let movedDocs = de.data.draggedDocuments;// de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments; - // note that it's possible the drag function might create a drop document that's not the same as the - // original dragged document. So we explicitly call addDocument() with a droppedDocument and + let movedDocs = de.data.draggedDocuments; added = movedDocs.reduce((added: boolean, d, i) => - de.data.moveDocument(d, this.props.Document, (doc: Doc) => this.props.addDocument(de.data.droppedDocuments[i])) || added, false); + de.data.droppedDocuments[i] !== d ? this.props.addDocument(de.data.droppedDocuments[i]) : + de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); } else { added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 1c2ed33eb..848afccf3 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -35,7 +35,7 @@ export class FontIconBox extends DocComponent( this._backgroundReaction && this._backgroundReaction(); } render() { - return ; diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index dd909e09f..deb98dc8d 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -7,6 +7,7 @@ overflow: hidden; position:absolute; z-index: -1; + cursor:auto; } .pdfBox-title-outer { diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss index b06d19c53..44e075153 100644 --- a/src/client/views/pdf/PDFMenu.scss +++ b/src/client/views/pdf/PDFMenu.scss @@ -15,7 +15,7 @@ } .pdfMenu-button:hover { - background-color: #121212; + background-color: #d4d4d4; } .pdfMenu-dragger { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 4abf85381..0fe8d996f 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -47,20 +47,20 @@ export class CurrentUserUtils { // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static setupCreatorButtons(doc: Doc) { doc.activePen = doc; - let docProtoData: { title: string, icon: string, drag?: string, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [ - { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' }, - { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' }, - { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' }, - { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, - { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, - { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, - { title: "pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, - { title: "highlighter", icon: "pen", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, - { title: "eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, - { title: "none", icon: "pause", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + let docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [ + { title: "collection", icon: "folder", ignoreClick: true, drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' }, + { title: "web page", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' }, + { title: "cat image", icon: "cat", ignoreClick: true, drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' }, + { title: "clickable button", icon: "bolt", ignoreClick: true, drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, + { title: "presentation", icon: "tv", ignoreClick: true, drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, + { title: "import folder", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, + { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, backgroundColor: "pink", activePen: doc }, + { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this) && this.activePen.pen !== undefined`, backgroundColor: "white", activePen: doc }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ - nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, + nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, ignoreClick: data.ignoreClick, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen, backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]) @@ -144,9 +144,9 @@ export class CurrentUserUtils { /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupExpandingButtons(doc: Doc) { doc.undoBtn = Docs.Create.FontIconDocument( - { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), removeDropProperties: new List(["dropAction"]), title: "undo button", icon: "undo-alt" }); doc.redoBtn = Docs.Create.FontIconDocument( - { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), removeDropProperties: new List(["dropAction"]), title: "redo button", icon: "redo-alt" }); doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], { title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", @@ -192,8 +192,8 @@ export class CurrentUserUtils { }); // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => (doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => (doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); + doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); + doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); return doc; } -- cgit v1.2.3-70-g09d2 From 57ccb3e236a7dad71848a06dad757c5a5a081ce8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 12:34:47 -0400 Subject: starting to switch document embedding to alias dragging. --- src/client/util/DragManager.ts | 1 + src/client/views/DocumentButtonBar.tsx | 21 ++++++++++----------- src/client/views/collections/CollectionSubView.tsx | 3 +++ .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +++++++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 13 +++++-------- 5 files changed, 34 insertions(+), 19 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index dcd19e50b..29a75b9ae 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -311,6 +311,7 @@ export namespace DragManager { this.urlField = embeddableSourceDoc.data instanceof URLField ? embeddableSourceDoc.data : undefined; } embeddableSourceDoc: Doc; + embeddedDoc?: Doc; urlField?: URLField; [id: string]: any; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 9e2d41621..5561d699c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -164,8 +164,12 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let dragDocView = this.props.views[0]; let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); + dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + offsetX: dragData.offset[0], + offsetY: dragData.offset[1], handlers: { dragComplete: action(emptyFunction), }, @@ -199,17 +203,12 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], e.stopPropagation(); } - considerEmbed = () => { - let thisDoc = this.props.views[0].props.Document; - let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; - // if (!canEmbed) return (null); - return ( -
-
- -
+ embedDragger = () => { + return (
+
+
- ); +
); } private get targetDoc() { @@ -353,7 +352,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
{this.metadataMenu} - {this.considerEmbed()} + {this.embedDragger()} {this.considerGoogleDocsPush()} {this.considerGoogleDocsPull()} { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 9919a9dc3..077142e88 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -143,6 +143,9 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); return added; } + else if (de.data instanceof DragManager.EmbedDragData) { + return this.props.addDocument(de.data.embeddedDoc = Doc.MakeAlias(de.data.embeddableSourceDoc)); + } else if (de.data instanceof DragManager.AnnotationDragData) { e.stopPropagation(); return this.props.addDocument(de.data.dropDocument); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index adbad5da5..46aaed509 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,6 +153,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); } } + else if (de.data instanceof DragManager.EmbedDragData && de.data.embeddedDoc) { + const d = de.data.embeddedDoc; + d.x = xp; + d.y = yp; + if (!NumCast(d.width)) { + d.width = 300; + } + if (!NumCast(d.height)) { + let nw = NumCast(d.nativeWidth); + let nh = NumCast(d.nativeHeight); + d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; + } + this.bringToFront(d); + this.updateCluster(d); + } else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1bdff3ec7..8153cf61c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -249,16 +249,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - // We're dealing with a link to a document - if (de.data instanceof DragManager.EmbedDragData) { - let target = de.data.embeddableSourceDoc; - // We're dealing with an internal document drop - const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); - let alias = Doc.MakeAlias(target); - alias.fitToBox = true; + if (de.data instanceof DragManager.EmbedDragData || (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction)) { + let target = de.data instanceof DragManager.DocumentDragData ? de.data.droppedDocuments[0] : Doc.MakeAlias(de.data.embeddableSourceDoc); + const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + target.fitToBox = true; let node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: alias[Id], + title: "dashDoc", docid: target[Id], float: "right" }); let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); -- cgit v1.2.3-70-g09d2 From fb817995eb94727b11324f298d0a30eebda8dcb7 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 12:47:09 -0400 Subject: removed DragEmbed -- subsumed by dragging aliases. --- src/client/util/DragManager.ts | 18 ++-------- src/client/views/DocumentButtonBar.tsx | 39 +++++++++++----------- src/client/views/collections/CollectionSubView.tsx | 3 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 --------- src/client/views/nodes/FormattedTextBox.tsx | 4 +-- 5 files changed, 23 insertions(+), 56 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 29a75b9ae..12e0b11ba 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -211,6 +211,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; + forceUserDropAction: dropActionType; moveDocument?: MoveFunction; isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; @@ -305,17 +306,6 @@ export namespace DragManager { [id: string]: any; } - export class EmbedDragData { - constructor(embeddableSourceDoc: Doc) { - this.embeddableSourceDoc = embeddableSourceDoc; - this.urlField = embeddableSourceDoc.data instanceof URLField ? embeddableSourceDoc.data : undefined; - } - embeddableSourceDoc: Doc; - embeddedDoc?: Doc; - urlField?: URLField; - [id: string]: any; - } - // for column dragging in schema view export class ColumnDragData { constructor(colKey: SchemaHeaderField) { @@ -329,10 +319,6 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } - export function StartEmbedDrag(ele: HTMLElement, dragData: EmbedDragData, downX: number, downY: number, options?: DragOptions) { - StartDrag([ele], dragData, downX, downY, options); - } - export function StartColumnDrag(ele: HTMLElement, dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) { StartDrag([ele], dragData, downX, downY, options); } @@ -428,7 +414,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined; + dragData.userDropAction = dragData.forceUserDropAction ? dragData.forceUserDropAction : e.ctrlKey || e.altKey ? "alias" : undefined; } if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 5561d699c..a00a4298f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -6,7 +6,6 @@ import { observer } from "mobx-react"; import { Doc } from "../../new_fields/Doc"; import { RichTextField } from '../../new_fields/RichTextField'; import { NumCast } from "../../new_fields/Types"; -import { URLField } from '../../new_fields/URLField'; import { emptyFunction } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; @@ -45,7 +44,7 @@ const fetch: IconProp = "sync-alt"; export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> { private _linkButton = React.createRef(); private _linkerButton = React.createRef(); - private _embedButton = React.createRef(); + private _aliasButton = React.createRef(); private _tooltipoff = React.createRef(); private _textDoc?: Doc; private _linkDrag?: UndoManager.Batch; @@ -111,13 +110,13 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], document.addEventListener("pointerup", this.onLinkerButtonUp); } - onEmbedButtonDown = (e: React.PointerEvent): void => { + onAliasButtonDown = (e: React.PointerEvent): void => { e.stopPropagation(); e.preventDefault(); - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.addEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); - document.addEventListener("pointerup", this.onEmbedButtonUp); + document.removeEventListener("pointermove", this.onAliasButtonMoved); + document.addEventListener("pointermove", this.onAliasButtonMoved); + document.removeEventListener("pointerup", this.onAliasButtonUp); + document.addEventListener("pointerup", this.onAliasButtonUp); } onLinkerButtonUp = (e: PointerEvent): void => { @@ -126,9 +125,9 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], e.stopPropagation(); } - onEmbedButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); + onAliasButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onAliasButtonMoved); + document.removeEventListener("pointerup", this.onAliasButtonUp); e.stopPropagation(); } @@ -157,17 +156,17 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], } @action - onEmbedButtonMoved = (e: PointerEvent): void => { - if (this._embedButton.current !== null) { - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); + onAliasButtonMoved = (e: PointerEvent): void => { + if (this._aliasButton.current !== null) { + document.removeEventListener("pointermove", this.onAliasButtonMoved); + document.removeEventListener("pointerup", this.onAliasButtonUp); let dragDocView = this.props.views[0]; - let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); - - DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + dragData.forceUserDropAction = "alias"; + DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, { offsetX: dragData.offset[0], offsetY: dragData.offset[1], handlers: { @@ -203,9 +202,9 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], e.stopPropagation(); } - embedDragger = () => { + aliasDragger = () => { return (
-
+
); @@ -352,7 +351,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
{this.metadataMenu} - {this.embedDragger()} + {this.aliasDragger()} {this.considerGoogleDocsPush()} {this.considerGoogleDocsPull()} { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 077142e88..9919a9dc3 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -143,9 +143,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); return added; } - else if (de.data instanceof DragManager.EmbedDragData) { - return this.props.addDocument(de.data.embeddedDoc = Doc.MakeAlias(de.data.embeddableSourceDoc)); - } else if (de.data instanceof DragManager.AnnotationDragData) { e.stopPropagation(); return this.props.addDocument(de.data.dropDocument); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 46aaed509..adbad5da5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,21 +153,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); } } - else if (de.data instanceof DragManager.EmbedDragData && de.data.embeddedDoc) { - const d = de.data.embeddedDoc; - d.x = xp; - d.y = yp; - if (!NumCast(d.width)) { - d.width = 300; - } - if (!NumCast(d.height)) { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; - } - this.bringToFront(d); - this.updateCluster(d); - } else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8153cf61c..ae67e94cc 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -249,8 +249,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.EmbedDragData || (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction)) { - let target = de.data instanceof DragManager.DocumentDragData ? de.data.droppedDocuments[0] : Doc.MakeAlias(de.data.embeddableSourceDoc); + if (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction) { + let target = de.data.droppedDocuments[0]; const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); target.fitToBox = true; let node = schema.nodes.dashDoc.create({ -- cgit v1.2.3-70-g09d2 From 8a3cfa8d6e72c9bfea4b38760e0b138b6525574c Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 16:42:37 -0400 Subject: a bunch of fixes to layouts to support templates in paticular. --- src/client/util/DocumentManager.ts | 2 + src/client/util/DragManager.ts | 4 +- src/client/views/CollectionLinearView.tsx | 7 +- src/client/views/DocumentButtonBar.tsx | 3 +- src/client/views/DocumentDecorations.tsx | 47 +++++----- src/client/views/InkingControl.tsx | 9 +- .../views/collections/CollectionStackingView.tsx | 11 ++- .../views/collections/CollectionTreeView.scss | 1 + .../views/collections/CollectionTreeView.tsx | 17 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 63 ++++++------- .../collections/collectionFreeForm/MarqueeView.tsx | 31 ++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/FontIconBox.tsx | 5 +- src/client/views/nodes/FormattedTextBox.tsx | 101 +++++++++++---------- src/new_fields/Doc.ts | 11 ++- 16 files changed, 178 insertions(+), 144 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 00de39671..e23ac55c2 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -72,6 +72,8 @@ export class DocumentManager { toReturn = view; } }); + } else { + break; } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 12e0b11ba..06dab024e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -211,7 +211,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; - forceUserDropAction: dropActionType; + embedDoc?: boolean; moveDocument?: MoveFunction; isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; @@ -414,7 +414,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = dragData.forceUserDropAction ? dragData.forceUserDropAction : e.ctrlKey || e.altKey ? "alias" : undefined; + dragData.userDropAction = e.ctrlKey ? "alias" : undefined; } if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index af3b194ea..44d9b042e 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -15,6 +15,7 @@ import { CollectionViewType } from './collections/CollectionBaseView'; import { CollectionSubView } from './collections/CollectionSubView'; import { documentSchema, DocumentView } from './nodes/DocumentView'; import { translate } from 'googleapis/build/src/apis/translate'; +import { DocumentType } from '../documents/DocumentTypes'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -50,8 +51,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" }); - dbox.dragFactory = doc; + let template = doc.layout instanceof Doc && doc.layout.isTemplate ? doc.layout : doc; + template.isTemplate = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; + dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplate ? "font" : "bolt" }); + dbox.dragFactory = template; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); } else if (doc.viewType === CollectionViewType.Linear) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a00a4298f..959b120ed 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -165,7 +165,8 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); - dragData.forceUserDropAction = "alias"; + dragData.embedDoc = true; + dragData.dropAction = "alias"; DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, { offsetX: dragData.offset[0], offsetY: dragData.offset[1], diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 755739a11..2f40ea746 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -471,47 +471,48 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); + let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document); let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; - let width = (doc.width || 0); - let height = (doc.height || (nheight / nwidth * width)); + let width = (layoutDoc.width || 0); + let height = (layoutDoc.height || (nheight / nwidth * width)); let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - doc.x = (doc.x || 0) + dX * (actualdW - width); - doc.y = (doc.y || 0) + dY * (actualdH - height); + layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width); + layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... - let fixedAspect = e.ctrlKey || (!doc.ignoreAspect && nwidth && nheight); - if (fixedAspect && e.ctrlKey && doc.ignoreAspect) { - doc.ignoreAspect = false; - proto.nativeWidth = nwidth = doc.width || 0; - proto.nativeHeight = nheight = doc.height || 0; + let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); + if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { + layoutDoc.ignoreAspect = false; + proto.nativeWidth = nwidth = layoutDoc.width || 0; + proto.nativeHeight = nheight = layoutDoc.height || 0; } if (fixedAspect && (!nwidth || !nheight)) { - proto.nativeWidth = nwidth = doc.width || 0; - proto.nativeHeight = nheight = doc.height || 0; + proto.nativeWidth = nwidth = layoutDoc.width || 0; + proto.nativeHeight = nheight = layoutDoc.height || 0; } - if (nwidth > 0 && nheight > 0 && !BoolCast(doc.ignoreAspect)) { + if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) { if (Math.abs(dW) > Math.abs(dH)) { if (!fixedAspect) { - Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true); + Doc.SetInPlace(doc, "nativeWidth", actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0), true); } - doc.width = actualdW; - if (fixedAspect && !doc.fitWidth) doc.height = nheight / nwidth * doc.width; - else doc.height = actualdH; + layoutDoc.width = actualdW; + if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width; + else layoutDoc.height = actualdH; } else { if (!fixedAspect) { - Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true); + Doc.SetInPlace(doc, "nativeHeight", actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0), true); } - doc.height = actualdH; - if (fixedAspect && !doc.fitWidth) doc.width = nwidth / nheight * doc.height; - else doc.width = actualdW; + layoutDoc.height = actualdH; + if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height; + else layoutDoc.width = actualdW; } } else { - dW && (doc.width = actualdW); - dH && (doc.height = actualdH); - dH && element.props.Document.autoHeight && Doc.SetInPlace(element.props.Document, "autoHeight", false, true); + dW && (layoutDoc.width = actualdW); + dH && (layoutDoc.height = actualdH); + dH && layoutDoc.autoHeight && Doc.SetInPlace(layoutDoc, "autoHeight", false, true); } } }); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 51fc7ca8f..46c6fae1c 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -41,7 +41,9 @@ export class InkingControl { if (InkingControl.Instance.selectedTool === InkTool.None) { let selected = SelectionManager.SelectedDocuments(); let oldColors = selected.map(view => { - let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); + let targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory : + view.props.Document.layout instanceof Doc ? view.props.Document.layout : + view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let sel = window.getSelection(); if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) { targetDoc.color = this._selectedColor; @@ -79,7 +81,10 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - !ruleProvider && (targetDoc.backgroundColor = matchedColor); + if (!ruleProvider) { + if (targetDoc) + targetDoc.backgroundColor = matchedColor; + } return { target: targetDoc, diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 90fc9c3e0..cde1a5036 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -110,9 +110,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } componentDidMount() { - // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { - if (BoolCast(this.props.Document.autoHeight)) { + if (this.props.Document.autoHeight) { let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); if (this.isStackingView) { return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght, @@ -188,15 +187,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } getDocHeight(d?: Doc) { if (!d) return 0; + let layoutDoc = d.layout instanceof Doc ? d.layout : d; let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); - if (!d.ignoreAspect && !d.fitWidth && nw && nh) { + if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) { let aspect = nw && nh ? nh / nw : 1; - if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid); + if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid); return wid * aspect; } - return d.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : Math.min(wid * NumCast(d.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : d[HeightSym](); + return layoutDoc.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : + Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym](); } columnDividerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index ca0c321b7..bff8ce5c1 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -16,6 +16,7 @@ background: $light-color-secondary; font-size: 13px; overflow: auto; + cursor: default; ul { list-style: none; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index abaa9662c..403da0e54 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -251,19 +251,21 @@ class TreeView extends React.Component { } docWidth = () => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); - return NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; + let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); + return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } docHeight = () => { let bounds = this.boundsOfCollectionDocument; return Math.min(this.MAX_EMBED_HEIGHT, (() => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); + let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); - return this.props.document.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : - Math.min(this.docWidth() * NumCast(this.props.document.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth, + return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth, NumCast(this.props.containingCollection.height)))) : - NumCast(this.props.document.height) ? NumCast(this.props.document.height) : 50; + NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50; })()); } @@ -461,10 +463,11 @@ class TreeView extends React.Component { let rowWidth = () => panelWidth() - 20; return docs.map((child, i) => { - let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); + const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); if (!pair.layout || pair.data instanceof Promise) { return (null); } + const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; let indent = i === 0 ? undefined : () => { if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { @@ -482,7 +485,7 @@ class TreeView extends React.Component { }; let rowHeight = () => { let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0); - return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym](); + return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); }; return { - d.x = x + NumCast(d.x) - dropX; - d.y = y + NumCast(d.y) - dropY; - if (!NumCast(d.width)) { - d.width = 300; + let layoutDoc = d.layout instanceof Doc ? d.layout : d; + layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; + layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + if (!NumCast(layoutDoc.width)) { + layoutDoc.width = 300; } if (!NumCast(d.height)) { let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); - d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; + layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); })); @@ -156,14 +157,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; + let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc; let x = xp - de.data.offset[0]; let y = yp - de.data.offset[1]; - let dropX = NumCast(de.data.dropDocument.x); - let dropY = NumCast(de.data.dropDocument.y); - dragDoc.x = x + NumCast(dragDoc.x) - dropX; - dragDoc.y = y + NumCast(dragDoc.y) - dropY; - de.data.targetContext = this.props.Document; - dragDoc.targetContext = this.props.Document; + let dropX = NumCast(layoutDoc.x); + let dropY = NumCast(layoutDoc.y); + layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; + layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); } } @@ -173,11 +174,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - let cx = NumCast(cd.x) - this._clusterDistance; - let cy = NumCast(cd.y) - this._clusterDistance; - let cw = NumCast(cd.width) + 2 * this._clusterDistance; - let ch = NumCast(cd.height) + 2 * this._clusterDistance; - return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? + let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd; + let cx = NumCast(layoutDoc.x) - this._clusterDistance; + let cy = NumCast(layoutDoc.y) - this._clusterDistance; + let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance; + let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance; + return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? NumCast(cd.cluster) : cluster; }, -1); } @@ -185,14 +187,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); if (cluster !== -1) { let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); - - // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view - let prevSelected = SelectionManager.SelectedDocuments(); - this.selectDocuments(eles); - let clusterDocs = SelectionManager.SelectedDocuments(); - SelectionManager.DeselectAll(); - prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - + let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); let de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); @@ -223,8 +218,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; this._clusterSets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + let memberLayout = member.layout instanceof Doc ? member.layout : member; + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) { doc.cluster = i; } })); @@ -296,10 +293,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childLayoutPairs.map(pair => pair.layout); + let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { - PDFMenu.Instance.fadeOut(true); let minx = docs.length ? NumCast(docs[0].x) : 0; let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; let miny = docs.length ? NumCast(docs[0].y) : 0; @@ -549,7 +545,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { - const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); + let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; + const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state }); state = pos.state === undefined ? state : pos.state; layoutPoolData.set(pair, pos); }); @@ -592,7 +589,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let i = 0; const width = Math.max(...docs.map(doc => NumCast(doc.width))); const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs) { + for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) { doc.x = x; doc.y = y; x += width + 20; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ecdd02b0f..1362736cf 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -281,7 +281,7 @@ export class MarqueeView extends React.Component let bounds = this.Bounds; let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => { + selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -325,7 +325,7 @@ export class MarqueeView extends React.Component this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => { + selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -394,20 +394,22 @@ export class MarqueeView extends React.Component let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } }); if (!selection.length && selectBackgrounds) { this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } @@ -420,10 +422,11 @@ export class MarqueeView extends React.Component let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { selection.push(doc); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 93f6dc468..5e8ac3ecd 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -36,8 +36,8 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); if (maxLocation === "inPlace") { expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc)); - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2); DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); } else { expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); @@ -392,7 +392,7 @@ export class DocumentView extends DocComponent(Docu if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) { let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(portalID).then(existingPortal => { - let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID }); + let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID }); DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link"); this.Document.isButton = true; }); @@ -491,7 +491,7 @@ export class DocumentView extends DocComponent(Docu layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }); } layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.Document.autoHeight = !this.Document.autoHeight, icon: "plus" }); + layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 848afccf3..c5bf28d5b 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -8,6 +8,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { StrCast } from '../../../new_fields/Types'; import { Utils } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; +import { Doc } from '../../../new_fields/Doc'; const FontIconSchema = createSchema({ icon: "string" }); @@ -35,8 +36,10 @@ export class FontIconBox extends DocComponent( this._backgroundReaction && this._backgroundReaction(); } render() { + let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); + let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc; return ; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ae67e94cc..f157a953e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -127,7 +127,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let view = this._editorView!; if (view.state.selection.from === view.state.selection.to) return false; if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.props.Document.color = color; + this.layoutDoc.color = color; } let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); @@ -143,7 +143,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } + + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document + @computed get layoutDoc(): Doc { + return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + } linkOnDeselect: Map = new Map(); @@ -249,39 +255,41 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction) { - let target = de.data.droppedDocuments[0]; - const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - target.fitToBox = true; - let node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], - float: "right" - }); - let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node)); - this.tryUpdateHeight(); - e.stopPropagation(); - } else if (de.data instanceof DragManager.DocumentDragData) { + if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; - if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) { - if (de.mods === "AltKey") { - if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); - e.stopPropagation(); - } - } else if (de.mods === "CtrlKey") { - draggedDoc.isTemplate = true; - if (typeof (draggedDoc.layout) === "string") { - let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); - layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); - this.props.Document.layout = layoutDelegateToOverrideFieldKey; - } else { - this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; - } + // replace text contents whend dragging with Alt + if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") { + if (draggedDoc.data instanceof RichTextField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); + e.stopPropagation(); + } + // apply as template when dragging with Meta + } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") { + draggedDoc.isTemplate = true; + let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + if (typeof (draggedDoc.layout) === "string") { + newLayout = Doc.MakeDelegate(draggedDoc); + newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); } + this.props.Document.layout = newLayout; e.stopPropagation(); - } + // embed document when dragging with a userDropAction or an embedDoc flag set + } else if (de.data.userDropAction || de.data.embedDoc) { + let target = de.data.droppedDocuments[0]; + const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + if (link) { + target.fitToBox = true; + let node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "right" + }); + let view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + this.tryUpdateHeight(); + e.stopPropagation(); + } + } // otherwise, fall through to outer collection to handle drop } } @@ -486,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ); this._heightReactionDisposer = reaction( - () => [this.props.Document[WidthSym](), this.props.Document.autoHeight], + () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight], () => this.tryUpdateHeight() ); @@ -505,7 +513,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); this._searchReactionDisposer = reaction(() => { - return StrCast(this.props.Document.search_string); + return StrCast(this.layoutDoc.search_string); }, searchString => { if (searchString) { this.highlightSearchTerms([searchString]); @@ -518,7 +526,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._rulesReactionDisposer = reaction(() => { let ruleProvider = this.props.ruleProvider; - let heading = NumCast(this.props.Document.heading); + let heading = NumCast(this.layoutDoc.heading); if (ruleProvider instanceof Doc) { return { align: StrCast(ruleProvider["ruleAlign_" + heading], ""), @@ -530,7 +538,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, action((rules: any) => { this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13); + this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13); rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { @@ -547,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }), { fireImmediately: true } ); this._scrollToRegionReactionDisposer = reaction( - () => StrCast(this.props.Document.scrollToLinkID), + () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { let findLinkFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -585,7 +593,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); setTimeout(() => this.unhighlightSearchTerms(), 2000); } - this.props.Document.scrollToLinkID = undefined; + this.layoutDoc.scrollToLinkID = undefined; } }, @@ -885,7 +893,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } } else { - let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) }); + let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); this.props.addDocument && this.props.addDocument(webDoc); } e.stopPropagation(); @@ -983,19 +991,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.Document.isAnimating && this.props.Document.autoHeight && scrollHeight !== 0 && + if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); - let dh = NumCast(this.props.Document.height, 0); - this.props.Document.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + let dh = NumCast(this.layoutDoc.height, 0); + this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; } } render() { let style = "hidden"; - let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground + let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); if (this.props.isSelected()) { @@ -1004,8 +1012,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return (
(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); -- cgit v1.2.3-70-g09d2 From dcdefb2a5a80d3c4d5451d6c7fc7213565d5ea5f Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 17 Oct 2019 16:02:37 -0400 Subject: initial working version of draggable link anchors. --- src/Utils.ts | 23 +++++++ src/client/util/DragManager.ts | 4 +- src/client/views/DocumentButtonBar.tsx | 10 +-- src/client/views/DocumentDecorations.tsx | 3 +- src/client/views/InkingControl.tsx | 12 ++-- src/client/views/nodes/DocuLinkView.tsx | 85 +++++++++++++++++++++++ src/client/views/nodes/DocumentView.tsx | 114 +++++++------------------------ 7 files changed, 145 insertions(+), 106 deletions(-) create mode 100644 src/client/views/nodes/DocuLinkView.tsx (limited to 'src/client/util') diff --git a/src/Utils.ts b/src/Utils.ts index 6889936b8..66ed8be5d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -149,6 +149,29 @@ export namespace Utils { } + export function clamp(n: number, lower: number, upper: number) { + return Math.max(lower, Math.min(upper, n)); + } + + export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) { + var r = l + w, + b = t + h; + + var x = clamp(x, l, r), + y = clamp(y, t, b); + + var dl = Math.abs(x - l), + dr = Math.abs(x - r), + dt = Math.abs(y - t), + db = Math.abs(y - b); + + var m = Math.min(dl, dr, dt, db); + + return (m === dt) ? [x, t] : + (m === db) ? [x, b] : + (m === dl) ? [l, y] : [r, y]; + } + export function GetClipboardText(): string { var textArea = document.createElement("textarea"); document.body.appendChild(textArea); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 06dab024e..576b16bc8 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -87,12 +87,12 @@ export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: num } } -export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) { +export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc, singleLink?: Doc) { let srcTarg = sourceDoc.proto; let draggedDocs: Doc[] = []; if (srcTarg) { - let linkDocs = LinkManager.Instance.getAllRelatedLinks(srcTarg); + let linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg); if (linkDocs) { draggedDocs = linkDocs.map(link => { let opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 959b120ed..ba87ecfb4 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -47,7 +47,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], private _aliasButton = React.createRef(); private _tooltipoff = React.createRef(); private _textDoc?: Doc; - private _linkDrag?: UndoManager.Batch; public static Instance: DocumentButtonBar; constructor(props: { views: DocumentView[] }) { @@ -139,15 +138,10 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let selDoc = this.props.views[0]; let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined; let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); - this._linkDrag = UndoManager.StartBatch("Drag Link"); + let _linkDrag = UndoManager.StartBatch("Drag Link"); DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { handlers: { - dragComplete: () => { - if (this._linkDrag) { - this._linkDrag.end(); - this._linkDrag = undefined; - } - }, + dragComplete: () => _linkDrag && _linkDrag.end() }, hideSource: false }); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6e8ba2d3d..927729487 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -167,7 +167,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> get Bounds(): { x: number, y: number, b: number, r: number } { let x = this._forceUpdate; this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { - if (documentView.props.renderDepth === 0 || + if (documentView._selectedLink !== -1 || + documentView.props.renderDepth === 0 || Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) { return bounds; } diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index b8f3c2875..1aacdc8ec 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -81,18 +81,18 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - (!ruleProvider && targetDoc) && (targetDoc.backgroundColor = matchedColor); + (!ruleProvider && targetDoc) && (view.setBackround(matchedColor)); return { target: targetDoc, previous: oldColor }; }); - let captured = this._selectedColor; - UndoManager.AddEvent({ - undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous), - redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured) - }); + //let captured = this._selectedColor; + // UndoManager.AddEvent({ + // undo: () => oldColors.forEach(pair => pair.target.backgroundColor = pair.previous), + // redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured) + // }); } else { CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor); } diff --git a/src/client/views/nodes/DocuLinkView.tsx b/src/client/views/nodes/DocuLinkView.tsx new file mode 100644 index 000000000..622d41047 --- /dev/null +++ b/src/client/views/nodes/DocuLinkView.tsx @@ -0,0 +1,85 @@ +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, Opt } from "../../../new_fields/Doc"; +import { NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils } from '../../../Utils'; +import { DocumentManager } from "../../util/DocumentManager"; +import "./DocumentView.scss"; +import React = require("react"); +import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; +import { UndoManager } from "../../util/UndoManager"; + + +interface DocuLinkViewProps { + Document: Doc; + isSelected: () => boolean; + addDocTab: (doc: Doc, dataDoc: Opt, where: string) => void; + anchor: string; + otherAnchor: string; + scale: () => number; + contentDiv: HTMLDivElement | null; + link: Doc; + index: number; + selectedLink: () => number; + selectLink: (id: number) => void; + blacklist: Opt +} + +@observer +export class DocuLinkView extends React.Component { + _downx = 0; + _downy = 0; + @observable _x = 0; + @observable _y = 0; + @observable _selected = false; + _ref = React.createRef(); + + onPointerDown = (e: React.PointerEvent) => { + this._downx = e.clientX; + this._downy = e.clientY; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + } + onPointerMove = action((e: PointerEvent) => { + if (this.props.contentDiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) { + let bounds = this.props.contentDiv.getBoundingClientRect(); + let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); + let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); + let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)) + if (separation > 100) { + DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.Document, this.props.link); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } else if (dragdist > separation) { + this.props.link[this.props.anchor + "_x"] = (pt[0] - bounds.left) / bounds.width * 100; + this.props.link[this.props.anchor + "_y"] = (pt[1] - bounds.top) / bounds.height * 100; + } + } + }) + onPointerUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey)) { + this.props.selectLink(this.props.selectedLink() === this.props.index ? -1 : this.props.index); + } + } + onClick = (e: React.MouseEvent) => { + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey)) { + DocumentManager.Instance.FollowLink(this.props.link, this.props.link[this.props.anchor] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); + } + e.stopPropagation(); + } + render() { + let y = NumCast(this.props.link[this.props.anchor + "_y"], 100); + let x = NumCast(this.props.link[this.props.anchor + "_x"], 100); + let c = StrCast(this.props.link[this.props.anchor + "_background"], "lightblue"); + return
+ } +} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c217c9d86..d8a1841b1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,6 +40,7 @@ import SharingManager from '../../util/SharingManager'; import { Scripting } from '../../util/Scripting'; import { DictationOverlay } from '../DictationOverlay'; import { CollectionViewType } from '../collections/CollectionBaseView'; +import { DocuLinkView } from './DocuLinkView'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -241,6 +242,7 @@ export class DocumentView extends DocComponent(Docu onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble && e.button === 0) return; + runInAction(() => this._selectedLink = -1); this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; @@ -626,6 +628,25 @@ export class DocumentView extends DocComponent(Docu layoutKey="layout" DataDoc={this.props.DataDoc} />); } + linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor1" : "anchor2"; + linkOtherEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "anchor2" : "anchor1"; + public setBackround(color: string) { + let selLink = this._selectedLink !== -1 && DocListCast(this.Document.links)[this._selectedLink]; + if (selLink) { + let both = selLink["anchor1_background"] === selLink["anchor2_background"]; + selLink[this.linkEndpoint(selLink) + "_background"] = color; + both && (selLink[this.linkOtherEndpoint(selLink) + "_background"] = color); + } else { + this.Document.backgroundColor = color; + } + } + @observable _selectedLink = -1; + selectLink = action((which: number) => { + SelectionManager.SelectDoc(this, false); + this._selectedLink = which; + }) + selectedLink = () => this._selectedLink; + render() { if (!this.props.Document) return (null); let animDims = this.props.Document.animateToDimensions ? Array.from(Cast(this.props.Document.animateToDimensions, listSpec("number"))!) : undefined; @@ -696,7 +717,10 @@ export class DocumentView extends DocComponent(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > - {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => )} + {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => + )} {!showTitle && !showCaption ? this.Document.searchFields ? (
@@ -720,94 +744,6 @@ export class DocumentView extends DocComponent(Docu } } - -interface DocuLinkProps { - view: DocumentView; - link: Doc; - index: number; -} - -@observer -export class DocuLink extends React.Component { - _downx = 0; - _downy = 0; - @observable _x = 0; - @observable _y = 0; - - constructor(props: DocuLinkProps) { - super(props); - } - - clamp(n: number, lower: number, upper: number) { - return Math.max(lower, Math.min(upper, n)); - } - - getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) { - var r = l + w, - b = t + h; - - var x = this.clamp(x, l, r), - y = this.clamp(y, t, b); - - var dl = Math.abs(x - l), - dr = Math.abs(x - r), - dt = Math.abs(y - t), - db = Math.abs(y - b); - - var m = Math.min(dl, dr, dt, db); - - return (m === dt) ? [x, t] : - (m === db) ? [x, b] : - (m === dl) ? [l, y] : [r, y]; - } - - get linkEndpoint() { - return Doc.AreProtosEqual(this.props.view.props.Document, Cast(this.props.link.anchor1, Doc) as Doc) ? - "anchor1" : "anchor2"; - } - get linkOtherEndpoint() { - return !Doc.AreProtosEqual(this.props.view.props.Document, Cast(this.props.link.anchor1, Doc) as Doc) ? - "anchor1" : "anchor2"; - } - - onPointerDown = (e: React.PointerEvent) => { - this._downx = e.clientX; - this._downy = e.clientY; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - } - onPointerMove = action((e: PointerEvent) => { - if (this.props.view.ContentDiv && (Math.abs(e.clientX - this._downx) > 3 || Math.abs(e.clientY - this._downy) > 3)) { - let bounds = this.props.view.ContentDiv.getBoundingClientRect(); - let pt = this.getNearestPointInPerimeter(bounds.left - 25, bounds.top - 25, bounds.width, bounds.height, e.clientX, e.clientY); - this.props.link[this.linkEndpoint + "_x"] = (pt[0] - bounds.left) / bounds.width * 100; - this.props.link[this.linkEndpoint + "_y"] = (pt[1] - bounds.top) / bounds.height * 100; - } - }) - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - onClick = (e: React.MouseEvent) => { - if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3) { - DocumentManager.Instance.FollowLink(this.props.link, this.props.view.props.Document, document => this.props.view.props.addDocTab(document, undefined, "inTab"), false); - } - e.stopPropagation(); - } - render() { - return
- {this.props.index} -
- } -} - - export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField: string, oldLayout?: Doc) { let oldLayoutExt = oldLayout || await Cast(doc[oldLayoutField], Doc); if (oldLayoutExt) { -- cgit v1.2.3-70-g09d2 From 73cb8628d13cc71e58717e97ca073c8b3316dd63 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 17 Oct 2019 23:40:29 -0400 Subject: working version of link lines between anchors --- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 7 ++- src/client/util/LinkManager.ts | 6 ++- src/client/views/OverlayView.tsx | 2 + .../CollectionFreeFormLinkView.scss | 3 -- .../CollectionFreeFormLinkView.tsx | 63 ++++++---------------- .../CollectionFreeFormLinksView.scss | 5 +- .../CollectionFreeFormLinksView.tsx | 44 +++++---------- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 ++- src/client/views/nodes/DocuLinkBox.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/new_fields/Doc.ts | 2 +- 12 files changed, 50 insertions(+), 97 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index edc2ac653..d979e0a3f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -724,6 +724,7 @@ export namespace DocUtils { linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); linkDocProto.width = linkDocProto.height = 0; linkDocProto.isBackground = true; + linkDocProto.isButton = true; LinkManager.Instance.addLink(linkDocProto); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index e23ac55c2..fc406c6ab 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -9,6 +9,7 @@ import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { Scripting } from './Scripting'; import { SelectionManager } from './SelectionManager'; +import { DocumentType } from '../documents/DocumentTypes'; export class DocumentManager { @@ -110,14 +111,18 @@ export class DocumentManager { @computed public get LinkedDocumentViews() { + console.log("START"); let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)).reduce((pairs, dv) => { + console.log("DOC = " + dv.props.Document.title + " " + dv.props.Document.layout); let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document); pairs.push(...linksList.reduce((pairs, link) => { if (link) { let linkToDoc = LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); if (linkToDoc) { DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - pairs.push({ a: dv, b: docView1, l: link }); + if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) { + pairs.push({ a: dv, b: docView1, l: link }); + } }); } } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 8a668e8d8..35c0f023f 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -79,7 +79,7 @@ export class LinkManager { let related = LinkManager.Instance.getAllLinks().filter(link => { let protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null)); let protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null)); - return protomatch1 || protomatch2; + return protomatch1 || protomatch2 || Doc.AreProtosEqual(link, anchor); }); return related; } @@ -244,8 +244,10 @@ export class LinkManager { public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined { if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) { return Cast(linkDoc.anchor2, Doc, null); - } else { + } else if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor2, Doc, null))) { return Cast(linkDoc.anchor1, Doc, null); + } else if (Doc.AreProtosEqual(anchor, linkDoc)) { + return linkDoc; } } } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 95c7b2683..9869e24d1 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -12,6 +12,7 @@ import { Transform } from "../util/Transform"; import { CollectionFreeFormDocumentView } from "./nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "./nodes/DocumentContentsView"; import { NumCast } from "../../new_fields/Types"; +import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView"; export type OverlayDisposer = () => void; @@ -206,6 +207,7 @@ export class OverlayView extends React.Component {
{this._elements}
+ {this.overlayDocs}
); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index cfd18ad35..1f1bca2f2 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,6 +1,5 @@ .collectionfreeformlinkview-linkLine { stroke: black; - transform: translate(10000px,10000px); opacity: 0.8; pointer-events: all; stroke-width: 3px; @@ -8,13 +7,11 @@ .collectionfreeformlinkview-linkCircle { stroke: rgb(0,0,0); opacity: 0.5; - transform: translate(10000px,10000px); pointer-events: all; cursor: pointer; } .collectionfreeformlinkview-linkText { stroke: rgb(0,0,0); opacity: 0.5; - transform: translate(10000px,10000px); pointer-events: all; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index a59fda6d9..4e1faccd7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,63 +1,30 @@ import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; -import { BoolCast, NumCast, StrCast } from "../../../../new_fields/Types"; -import { InkingControl } from "../../InkingControl"; +import { Doc } from "../../../../new_fields/Doc"; +import { Utils } from '../../../../Utils'; +import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; import React = require("react"); import v5 = require("uuid/v5"); +import { DocumentType } from "../../../documents/DocumentTypes"; export interface CollectionFreeFormLinkViewProps { - A: Doc; - B: Doc; + A: DocumentView; + B: DocumentView; LinkDocs: Doc[]; - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; } @observer export class CollectionFreeFormLinkView extends React.Component { - onPointerDown = (e: React.PointerEvent) => { - if (e.button === 0 && !InkingControl.Instance.selectedTool) { - let a = this.props.A; - let b = this.props.B; - let x1 = NumCast(a.x) + (BoolCast(a.isMinimized) ? 5 : a[WidthSym]() / 2); - let y1 = NumCast(a.y) + (BoolCast(a.isMinimized) ? 5 : a[HeightSym]() / 2); - let x2 = NumCast(b.x) + (BoolCast(b.isMinimized) ? 5 : b[WidthSym]() / 2); - let y2 = NumCast(b.y) + (BoolCast(b.isMinimized) ? 5 : b[HeightSym]() / 2); - // this.props.LinkDocs.map(l => { - // let width = l[WidthSym](); - // l.x = (x1 + x2) / 2 - width / 2; - // l.y = (y1 + y2) / 2 + 10; - // if (!this.props.removeDocument(l)) this.props.addDocument(l, false); - // }); - e.stopPropagation(); - e.preventDefault(); - } - } render() { - // let l = this.props.LinkDocs; - let a = this.props.A.layout instanceof Doc ? this.props.A.layout : this.props.A; - let b = this.props.B.layout instanceof Doc ? this.props.B.layout : this.props.B; - let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2); - let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2); - let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2); - let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2); - let text = ""; - // let first = this.props.LinkDocs[0]; - // if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); - // else text = "-multiple-"; - return ( - <> - - {/* */} - - {text} - - - ); + let acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; + let bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; + let a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect(); + let b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect(); + let pt1 = Utils.getNearestPointInPerimeter(a.left, a.top, a.width, a.height, b.left + b.width / 2, b.top + b.height / 2); + let pt2 = Utils.getNearestPointInPerimeter(b.left, b.top, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2); + return (); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss index 30e158603..cb5cef29c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss @@ -1,10 +1,9 @@ .collectionfreeformlinksview-svgCanvas{ - transform: translate(-10000px,-10000px); position: absolute; top: 0; left: 0; - width: 20000px; - height: 20000px; + width: 100%; + height: 100%; pointer-events: none; } .collectionfreeformlinksview-container { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 189981e35..cf0e55ccf 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,17 +1,17 @@ import { computed, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { Cast, FieldValue } from "../../../../new_fields/Types"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; -import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); +import { Utils } from "../../../../Utils"; +import { SelectionManager } from "../../../util/SelectionManager"; @observer -export class CollectionFreeFormLinksView extends React.Component { +export class CollectionFreeFormLinksView extends React.Component { _brushReactionDisposer?: IReactionDisposer; componentDidMount() { @@ -71,35 +71,18 @@ export class CollectionFreeFormLinksView extends React.Component - child[Id] === collid).map(view => - DocumentManager.Instance.getDocumentViews(view).map(view => - equalViews.push(view))); - } - return equalViews.filter(sv => sv.props.ContainingCollectionDoc === this.props.Document); - } - @computed get uniqueConnections() { let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { - let srcViews = this.documentAnchors(connection.a); - let targetViews = this.documentAnchors(connection.b); + let srcViews = [connection.a]; + let targetViews = [connection.b]; - let possiblePairs: { a: Doc, b: Doc, }[] = []; - srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); + let possiblePairs: { a: DocumentView, b: DocumentView, }[] = []; + srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv, b: tv }))); possiblePairs.map(possiblePair => { if (!drawnPairs.reduce((found, drawnPair) => { - let match1 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.a) && Doc.AreProtosEqual(possiblePair.b, drawnPair.b)); - let match2 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.b) && Doc.AreProtosEqual(possiblePair.b, drawnPair.a)); + let match1 = (Doc.AreProtosEqual(possiblePair.a.props.Document, drawnPair.a.props.Document) && Doc.AreProtosEqual(possiblePair.b.props.Document, drawnPair.b.props.Document)); + let match2 = (Doc.AreProtosEqual(possiblePair.a.props.Document, drawnPair.b.props.Document) && Doc.AreProtosEqual(possiblePair.b.props.Document, drawnPair.a.props.Document)); let match = match1 || match2; if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) { drawnPair.l.push(connection.l); @@ -110,16 +93,15 @@ export class CollectionFreeFormLinksView extends React.Component p + l[Id], "")} A={c.a} B={c.b} LinkDocs={c.l} - removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />); + }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); + return connections.map(c => ); } render() { return (
- {this.uniqueConnections} + {SelectionManager.GetIsDragging() ? (null) : this.uniqueConnections} {this.props.children}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5dccb5bf0..b3270d507 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -705,11 +705,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> - - - {this.childViews} - - + + {this.childViews} + diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 8c41b9d4f..f56bf4ad3 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -54,12 +54,12 @@ export class DocuLinkBox extends DocComponent(Doc onPointerUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey)) { + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button === 2 || e.ctrlKey || !this.props.Document.isButton)) { this.props.select(false); } } onClick = (e: React.MouseEvent) => { - if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey)) { + if (Math.abs(e.clientX - this._downx) < 3 && Math.abs(e.clientY - this._downy) < 3 && (e.button !== 2 && !e.ctrlKey && this.props.Document.isButton)) { DocumentManager.Instance.FollowLink(this.props.Document, this.props.Document[this.props.fieldKey] as Doc, document => this.props.addDocTab(document, undefined, "inTab"), false); } e.stopPropagation(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 6775655eb..0bcaa99aa 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -697,7 +697,7 @@ export class DocumentView extends DocComponent(Docu opacity: this.Document.opacity }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={() => { Doc.UnBrushAllDocs(); Doc.BrushDoc(this.props.Document); }} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) =>
)} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 04f310785..26355d0e7 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -734,7 +734,7 @@ export namespace Doc { } export function UnBrushAllDocs() { - manager.BrushedDoc.clear(); + brushManager.BrushedDoc.clear(); } export function setChildLayout(target: Doc, source?: Doc) { -- cgit v1.2.3-70-g09d2 From f0e8502be6488418370d4cd3dbb6c60ffd30f658 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 18 Oct 2019 00:25:39 -0400 Subject: better version of link lines between anchors --- src/client/util/DocumentManager.ts | 37 ++++++---------------- .../CollectionFreeFormLinksView.tsx | 31 ++++++++---------- src/client/views/nodes/DocumentView.tsx | 10 ++++-- 3 files changed, 30 insertions(+), 48 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index fc406c6ab..aed7aafd1 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -42,8 +42,8 @@ export class DocumentManager { if (toReturn.length === 0) { DocumentManager.Instance.DocumentViews.map(view => { let doc = view.props.Document.proto; - if (doc && doc[Id]) { - if (doc[Id] === id) { toReturn.push(view); } + if (doc && doc[Id] && doc[Id] === id) { + toReturn.push(view); } }); } @@ -90,42 +90,25 @@ export class DocumentManager { return views.length ? views[0] : undefined; } public getDocumentViews(toFind: Doc): DocumentView[] { - let toReturn: DocumentView[] = []; - //gets document view that is in a freeform canvas collection - DocumentManager.Instance.DocumentViews.map(view => { - let doc = view.props.Document; - - if (doc === toFind) { - toReturn.push(view); - } else { - if (Doc.AreProtosEqual(doc, toFind)) { - toReturn.push(view); - } - } - }); + DocumentManager.Instance.DocumentViews.map(view => + Doc.AreProtosEqual(view.props.Document, toFind) && toReturn.push(view)); return toReturn; } @computed public get LinkedDocumentViews() { - console.log("START"); let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)).reduce((pairs, dv) => { - console.log("DOC = " + dv.props.Document.title + " " + dv.props.Document.layout); let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document); pairs.push(...linksList.reduce((pairs, link) => { - if (link) { - let linkToDoc = LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); - if (linkToDoc) { - DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) { - pairs.push({ a: dv, b: docView1, l: link }); - } - }); + let linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); + linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { + if (dv.props.Document.type !== DocumentType.LINK || dv.props.layoutKey !== docView1.props.layoutKey) { + pairs.push({ a: dv, b: docView1, l: link }); } - } + }); return pairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc }[])); // } @@ -135,8 +118,6 @@ export class DocumentManager { return pairs; } - - public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false): Promise => { let highlight = () => { const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index cf0e55ccf..a26febda4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -9,6 +9,7 @@ import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); import { Utils } from "../../../../Utils"; import { SelectionManager } from "../../../util/SelectionManager"; +import { DocumentType } from "../../../documents/DocumentTypes"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -74,27 +75,21 @@ export class CollectionFreeFormLinksView extends React.Component { @computed get uniqueConnections() { let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { - let srcViews = [connection.a]; - let targetViews = [connection.b]; - - let possiblePairs: { a: DocumentView, b: DocumentView, }[] = []; - srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv, b: tv }))); - possiblePairs.map(possiblePair => { - if (!drawnPairs.reduce((found, drawnPair) => { - let match1 = (Doc.AreProtosEqual(possiblePair.a.props.Document, drawnPair.a.props.Document) && Doc.AreProtosEqual(possiblePair.b.props.Document, drawnPair.b.props.Document)); - let match2 = (Doc.AreProtosEqual(possiblePair.a.props.Document, drawnPair.b.props.Document) && Doc.AreProtosEqual(possiblePair.b.props.Document, drawnPair.a.props.Document)); - let match = match1 || match2; - if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) { - drawnPair.l.push(connection.l); - } - return match || found; - }, false)) { - drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] }); + if (!drawnPairs.reduce((found, drawnPair) => { + let match1 = (connection.a === drawnPair.a && connection.b === drawnPair.b); + let match2 = (connection.a === drawnPair.b && connection.b === drawnPair.a); + let match = match1 || match2; + if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) { + drawnPair.l.push(connection.l); } - }); + return match || found; + }, false)) { + drawnPairs.push({ a: connection.a, b: connection.b, l: [connection.l] }); + } return drawnPairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); - return connections.map(c => ); + return connections.filter(c => c.a.props.Document.type === DocumentType.LINK) // get rid of the filter to show links to documents in addition to document anchors + .map(c => ); } render() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 0bcaa99aa..d4c2c512c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -697,10 +697,16 @@ export class DocumentView extends DocComponent(Docu opacity: this.Document.opacity }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={() => { Doc.UnBrushAllDocs(); Doc.BrushDoc(this.props.Document); }} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={() => { + Doc.UnBrushAllDocs(); + DocListCast(this.props.Document.links).map(Doc.BrushDoc); + Doc.BrushDoc(this.props.Document); + }} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => -
)} +
+ +
)} {!showTitle && !showCaption ? this.Document.searchFields ? (
-- cgit v1.2.3-70-g09d2 From c9a84a7d74efa820d1dc55d8ef93bec40315be4a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 18 Oct 2019 01:29:33 -0400 Subject: link lines are working properly except when things get panned/moved ... need to turn off or get updates somehow. --- src/client/documents/Documents.ts | 15 ++-------- src/client/util/DocumentManager.ts | 11 +++++-- src/client/views/DocumentDecorations.tsx | 3 +- .../CollectionFreeFormLinksView.tsx | 4 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 35 ---------------------- src/client/views/nodes/DocumentContentsView.tsx | 32 ++++++++------------ src/client/views/nodes/DocumentView.tsx | 9 ++---- src/new_fields/Doc.ts | 6 ---- 8 files changed, 28 insertions(+), 87 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d979e0a3f..b53549f7a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -80,7 +80,6 @@ export interface DocumentOptions { opacity?: number; defaultBackgroundColor?: string; dropAction?: dropActionType; - backgroundLayout?: string; chromeStatus?: string; columnWidth?: number; fontSize?: number; @@ -127,7 +126,6 @@ export namespace Docs { layout: { view: LayoutSource, ext?: string, // optional extension field for layout source - collectionView?: CollectionViewType }, options?: Partial }; @@ -142,7 +140,7 @@ export namespace Docs { options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" } }], [DocumentType.HIST, { - layout: { view: HistogramBox, collectionView: [CollectionView, data] as CollectionViewType }, + layout: { view: HistogramBox }, options: { height: 300, backgroundColor: "black" } }], [DocumentType.QUERY, { @@ -290,15 +288,8 @@ export namespace Docs { // synthesize the default options, the type and title from computed values and // whatever options pertain to this specific prototype let options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; - let primary = layout.view.LayoutString(layout.ext); - let collectionView = layout.collectionView; - if (collectionView) { - options.layout = collectionView[0].LayoutString(collectionView[1], collectionView[2]); - options.backgroundLayout = primary; - } else { - options.layout = primary; - } - return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: primary }); + options.layout = layout.view.LayoutString(layout.ext); + return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: options.layout }); } } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index aed7aafd1..ee6772f8f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,5 +1,5 @@ import { action, computed, observable } from 'mobx'; -import { Doc, DocListCastAsync } from '../../new_fields/Doc'; +import { Doc, DocListCastAsync, DocListCast } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { List } from '../../new_fields/List'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; @@ -100,7 +100,13 @@ export class DocumentManager { @computed public get LinkedDocumentViews() { - let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document)).reduce((pairs, dv) => { + let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document) + || DocumentManager.Instance.DocumentViews.some(dv2 => { + let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document); + let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document)); + return init && rest; + }) + ).reduce((pairs, dv) => { let linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document); pairs.push(...linksList.reduce((pairs, link) => { let linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); @@ -111,7 +117,6 @@ export class DocumentManager { }); return pairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc }[])); - // } return pairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc }[]); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 6e8ba2d3d..8409a34da 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -111,7 +111,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> Doc.GetProto(docTemplate).layoutNative = layoutNative; swapViews(fieldTemplate, "", "layoutNative", layoutNative); layoutNative.layout = StrCast(fieldTemplateView.props.Document.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); - layoutNative.backgroundLayout = StrCast(fieldTemplateView.props.Document.backgroundLayout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`); } } } @@ -343,7 +342,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc); if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) { - const layout = StrCast(doc.backgroundLayout, StrCast(doc.layout, FieldView.LayoutString(DocumentView))); + const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView)); iconDoc = this.createIcon([docView], layout); } return iconDoc; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a26febda4..e9191c176 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -68,9 +68,7 @@ export class CollectionFreeFormLinksView extends React.Component { // }); } componentWillUnmount() { - if (this._brushReactionDisposer) { - this._brushReactionDisposer(); - } + this._brushReactionDisposer && this._brushReactionDisposer(); } @computed get uniqueConnections() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b3270d507..932b6722b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -467,22 +467,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getScale: this.getScale }; } - getDocumentViewProps(layoutDoc: Doc): DocumentViewProps { - return { - ...this.props, - ScreenToLocalTransform: this.getTransform, - PanelWidth: layoutDoc[WidthSym], - PanelHeight: layoutDoc[HeightSym], - ContentScaling: returnOne, - ContainingCollectionView: this.props.ContainingCollectionView, - focus: this.focusDocument, - backgroundColor: returnEmptyString, - parentActive: this.props.active, - bringToFront: this.bringToFront, - zoomToScale: this.zoomToScale, - getScale: this.getScale - }; - } getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { const script = this.Document.arrangeScript; @@ -682,7 +666,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private childViews = () => { let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; return [ - , ...children, ...this.views, ]; @@ -712,29 +695,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { {this.overlayViews} -
); } } -@observer -class CollectionFreeFormOverlayView extends React.Component boolean }> { - render() { - return ; - } -} - -@observer -class CollectionFreeFormBackgroundView extends React.Component boolean }> { - render() { - return !this.props.Document.backgroundLayout ? (null) : - (); - } -} - interface CollectionFreeFormViewPannableContentsProps { centeringShiftX: () => number; centeringShiftY: () => number; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 4971e6ce5..d375405b9 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -92,26 +92,20 @@ export class DocumentContentsView extends React.Component" : this.layout; - } - render() { - let self = this; - if (this.props.renderDepth > 7) return (null); - if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null); - return 7 || !this.layout) ? (null) : + { console.log(test); }} - />; + onError={(test: any) => { console.log(test); }} + />; } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d4c2c512c..48ad7a632 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -163,6 +163,7 @@ export class DocumentView extends DocComponent(Docu @action componentWillUnmount() { this._dropDisposer && this._dropDisposer(); + Doc.UnBrushDoc(this.props.Document); DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } @@ -697,11 +698,7 @@ export class DocumentView extends DocComponent(Docu opacity: this.Document.opacity }} onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={() => { - Doc.UnBrushAllDocs(); - DocListCast(this.props.Document.links).map(Doc.BrushDoc); - Doc.BrushDoc(this.props.Document); - }} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) =>
@@ -739,7 +736,6 @@ export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField oldLayoutExt.nativeWidth = doc.nativeWidth; oldLayoutExt.nativeHeight = doc.nativeHeight; oldLayoutExt.ignoreAspect = doc.ignoreAspect; - oldLayoutExt.backgroundLayout = doc.backgroundLayout; oldLayoutExt.type = doc.type; oldLayoutExt.layout = doc.layout; } @@ -752,7 +748,6 @@ export async function swapViews(doc: Doc, newLayoutField: string, oldLayoutField doc.nativeWidth = newLayoutExt.nativeWidth; doc.nativeHeight = newLayoutExt.nativeHeight; doc.ignoreAspect = newLayoutExt.ignoreAspect; - doc.backgroundLayout = newLayoutExt.backgroundLayout; doc.type = newLayoutExt.type; doc.layout = await newLayoutExt.layout; } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 26355d0e7..69c048ebf 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -592,7 +592,6 @@ export namespace Doc { target.ignoreAspect = templateDoc.nativeWidth ? true : false; target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy](); target.layout = layoutCustomLayout; - target.backgroundLayout = layoutCustomLayout.backgroundLayout; target.layoutNative = Cast(templateDoc.layoutNative, Doc) as Doc; target.layoutCustom = layoutCustom; @@ -602,19 +601,14 @@ export namespace Doc { export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false): boolean { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, ""); - let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); let fieldLayoutDoc = fieldTemplate; if (fieldTemplate.layout instanceof Doc) { fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout); } - if (backgroundLayout) { - backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metadataFieldName}"}`); - } fieldTemplate.templateField = metadataFieldName; fieldTemplate.title = metadataFieldName; fieldTemplate.isTemplateField = true; - fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid inheriting them from the template's data doc which may also define these fields for its own use. */ -- cgit v1.2.3-70-g09d2 From e21810a4097e724a378416135c7cc6def7ff022c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 18 Oct 2019 11:35:22 -0400 Subject: cleaned up dictation into text notes --- src/client/util/DictationManager.ts | 3 +- src/client/views/MainView.tsx | 3 +- src/client/views/nodes/FormattedTextBox.scss | 16 ++++++---- src/client/views/nodes/FormattedTextBox.tsx | 46 +++++++++++++++++++++++----- 4 files changed, 53 insertions(+), 15 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 182cfb70a..ae991635f 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -64,7 +64,7 @@ export namespace DictationManager { const intraSession = ". "; const interSession = " ... "; - let isListening = false; + export let isListening = false; let isManuallyStopped = false; let current: string | undefined = undefined; @@ -200,6 +200,7 @@ export namespace DictationManager { if (!isListening || !recognizer) { return; } + isListening = false; isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); // let main = MainView.Instance; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0e15784f7..9304f4bef 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,7 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, - faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter + faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; @@ -106,6 +106,7 @@ export class MainView extends React.Component { library.add(faPen); library.add(faHighlighter); library.add(faEraser); + library.add(faFileAudio); library.add(faPenNib); library.add(faFilm); library.add(faMusic); diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 0550f9708..a4acd3b82 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -10,8 +10,7 @@ outline: none !important; } -.formattedTextBox-cont-scroll, -.formattedTextBox-cont-hidden { +.formattedTextBox-cont { cursor: text; background: inherit; padding: 0; @@ -26,10 +25,15 @@ color: initial; height: 100%; pointer-events: all; -} - -.formattedTextBox-cont-hidden { - // pointer-events: none; + overflow-y: auto; + max-height: 100%; + .formattedTextBox-dictation { + height: 20px; + width: 20px; + top: 0px; + left: 0px; + position: absolute; + } } .formattedTextBox-inner-rounded { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 045eee3f7..6f4ced7f3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,7 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; import _ from "lodash"; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -43,6 +43,8 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { siteVerification } from 'googleapis/build/src/apis/siteVerification'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -369,7 +371,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe specificContextMenu = (e: React.MouseEvent): void => { let funcs: ContextMenuProps[] = []; - funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); + funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { @@ -386,6 +388,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" }); } + @observable _recording = false; + + recordDictation = () => { + //this._editorView!.focus(); + if (this._recording) return; + runInAction(() => this._recording = true); + DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + this._editorView!.focus(); + }); + } + stopDictation = (abort: boolean) => { + runInAction(() => this._recording = false); + DictationManager.Controls.stop(!abort); + } + recordBullet = async () => { let completedCue = "end session"; let results = await DictationManager.Controls.listen({ @@ -902,6 +925,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey); + if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500); } // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. @@ -965,7 +989,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } onBlur = (e: any) => { - DictationManager.Controls.stop(false); + //DictationManager.Controls.stop(false); if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; @@ -986,6 +1010,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } + if (this._recording) { this.stopDictation(true); setTimeout(() => this.recordDictation(), 250); } } @action @@ -1001,7 +1026,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } render() { - let style = "hidden"; let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground ? "none" : "all"; @@ -1010,10 +1034,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); } return ( -
this._entered = false)} >
+ +
{ + this._recording ? this.stopDictation(true) : this.recordDictation(); + setTimeout(() => this._editorView!.focus(), 500); + e.stopPropagation(); + }} > + +
); } -- cgit v1.2.3-70-g09d2 From 11537da75c76fba79a2709d2ad175dfa16a25256 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 18 Oct 2019 14:00:25 -0400 Subject: fixes for drawing link anchors to pdf text selections. --- src/client/documents/DocumentTypes.ts | 3 ++- src/client/util/DocumentManager.ts | 9 +++++---- src/client/util/LinkManager.ts | 19 +++++++------------ src/client/views/MainView.tsx | 3 ++- src/client/views/nodes/DocuLinkBox.tsx | 7 +++++-- src/client/views/nodes/DocumentView.scss | 3 --- src/client/views/nodes/DocumentView.tsx | 2 ++ src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 6 ++++++ 9 files changed, 30 insertions(+), 24 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index ea37fc2f1..12501065a 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -23,5 +23,6 @@ export enum DocumentType { PRESELEMENT = "preselement", QUERY = "search", COLOR = "color", - DOCULINK = "doculink" + DOCULINK = "doculink", + PDFANNO = "pdfanno" } \ No newline at end of file diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ee6772f8f..0f2a47dd0 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -100,10 +100,11 @@ export class DocumentManager { @computed public get LinkedDocumentViews() { - let pairs = DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || Doc.IsBrushed(dv.props.Document) - || DocumentManager.Instance.DocumentViews.some(dv2 => { - let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document); - let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document)); + let pairs = DocumentManager.Instance.DocumentViews.filter(dv => + dv.isSelected() || Doc.IsBrushed(dv.props.Document) // draw links from DocumentViews that are selected or brushed OR + || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which + let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors + let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document); // on a view that is selected or brushed return init && rest; }) ).reduce((pairs, dv) => { diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 35c0f023f..ee2f2dadc 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,10 +1,7 @@ -import { observable, action } from "mobx"; -import { StrCast, Cast, FieldValue } from "../../new_fields/Types"; import { Doc, DocListCast } from "../../new_fields/Doc"; -import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; -import { Id } from "../../new_fields/FieldSymbols"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; +import { listSpec } from "../../new_fields/Schema"; +import { Cast, StrCast } from "../../new_fields/Types"; import { Docs } from "../documents/Documents"; import { Scripting } from "./Scripting"; @@ -242,13 +239,11 @@ export class LinkManager { //TODO This should probably return undefined if there isn't an opposite anchor //TODO This should also await the return value of the anchor so we don't filter out promises public getOppositeAnchor(linkDoc: Doc, anchor: Doc): Doc | undefined { - if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor1, Doc, null))) { - return Cast(linkDoc.anchor2, Doc, null); - } else if (Doc.AreProtosEqual(anchor, Cast(linkDoc.anchor2, Doc, null))) { - return Cast(linkDoc.anchor1, Doc, null); - } else if (Doc.AreProtosEqual(anchor, linkDoc)) { - return linkDoc; - } + let a1 = Cast(linkDoc.anchor1, Doc, null); + let a2 = Cast(linkDoc.anchor2, Doc, null); + if (Doc.AreProtosEqual(anchor, a1)) return a2; + if (Doc.AreProtosEqual(anchor, a2)) return a1; + if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc; } } Scripting.addGlobal(function links(doc: any) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9304f4bef..4c2b6f262 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -183,7 +183,8 @@ export class MainView extends React.Component { y: 400, width: this._panelWidth * .7, height: this._panelHeight, - title: "My Blank Collection" + title: "My Blank Collection", + backgroundColor: "white" }; let workspaces: FieldResult; let freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index f56bf4ad3..3294a5aa2 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -2,7 +2,7 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; -import { NumCast, StrCast } from "../../../new_fields/Types"; +import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { Utils } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragLinksAsDocuments } from "../../util/DragManager"; @@ -11,6 +11,7 @@ import { documentSchema } from "./DocumentView"; import "./DocuLinkBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); +import { DocumentType } from "../../documents/DocumentTypes"; type DocLinkSchema = makeInterface<[typeof documentSchema]>; const DocLinkDocument = makeInterface(documentSchema); @@ -65,13 +66,15 @@ export class DocuLinkBox extends DocComponent(Doc e.stopPropagation(); } render() { + let anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc); + let hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO; let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100); let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100); let c = StrCast(this.props.Document.backgroundColor, "lightblue"); return
; } } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 69eb1a843..a0bf74990 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -47,9 +47,6 @@ } } } -.documentView-node-topmost { - background: white; -} .documentView-styleWrapper { position: absolute; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 48ad7a632..8bf698391 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -629,6 +629,7 @@ export class DocumentView extends DocComponent(Docu DataDoc={this.props.DataDoc} />); } linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; + linkEndpointDoc = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor1, Doc) as Doc : Cast(linkDoc.anchor2, Doc) as Doc; render() { if (!this.props.Document) return (null); @@ -701,6 +702,7 @@ export class DocumentView extends DocComponent(Docu onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > {this.props.Document.links && DocListCast(this.props.Document.links).map((d, i) => + //this.linkEndpointDoc(d).type === DocumentType.PDFANNO ? (null) :
)} diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index ad6240c70..e0a3b9171 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -100,7 +100,7 @@ class RegionAnnotation extends React.Component { let annoGroup = await Cast(this.props.document.group, Doc); if (annoGroup) { DocumentManager.Instance.FollowLink(undefined, annoGroup, - (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"), + (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight"), false, false, undefined); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 1bae6128c..6e5f1a981 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -25,6 +25,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch } from "../../util/UndoManager"; import { DocAnnotatableComponent } from "../DocComponent"; import { documentSchema } from "../nodes/DocumentView"; +import { DocumentType } from "../../documents/DocumentTypes"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); @@ -249,6 +250,7 @@ export class PDFViewer extends DocAnnotatableComponent(annoDocs); } mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title); -- cgit v1.2.3-70-g09d2 From 1f3576a69a6a0396d07e965c700bb7d69d77a0a3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 18 Oct 2019 17:06:41 -0400 Subject: fixed up creator buttons - factored out convert to buttons code into a dropConverter script. fixed issues with template loading for images. --- src/client/documents/Documents.ts | 2 +- src/client/util/DragManager.ts | 3 ++ src/client/util/DropConverter.ts | 46 ++++++++++++++++++++++ src/client/views/CollectionLinearView.tsx | 40 ------------------- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 6 ++- .../views/collections/CollectionTreeView.scss | 1 - .../views/collections/CollectionTreeView.tsx | 2 +- .../CollectionFreeFormLinkView.tsx | 2 +- .../authentication/models/current_user_utils.ts | 11 ++++-- 10 files changed, 65 insertions(+), 50 deletions(-) create mode 100644 src/client/util/DropConverter.ts (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 651662ded..32fda1954 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -105,7 +105,7 @@ export interface DocumentOptions { yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts panel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script - convertToButtons?: boolean; // whether documents dropped onto a collection should be converted to buttons that will construct the dropped document + dropConverter?: ScriptField; // script to run when documents are dropped on this Document. // [key: string]: Opt; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 576b16bc8..bbc29585c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -14,6 +14,8 @@ import { ScriptField } from "../../new_fields/ScriptField"; import { List } from "../../new_fields/List"; import { PrefetchProxy } from "../../new_fields/Proxy"; import { listSpec } from "../../new_fields/Schema"; +import { Scripting } from "./Scripting"; +import { convertDropDataToButtons } from "./DropConverter"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag( @@ -497,3 +499,4 @@ export namespace DragManager { } } } +Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); }); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts new file mode 100644 index 000000000..eea3da1bc --- /dev/null +++ b/src/client/util/DropConverter.ts @@ -0,0 +1,46 @@ +import { DragManager } from "./DragManager"; +import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { Doc, DocListCast } from "../../new_fields/Doc"; +import { DocumentType } from "../documents/DocumentTypes"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { StrCast } from "../../new_fields/Types"; +import { Docs } from "../documents/Documents"; +import { ScriptField } from "../../new_fields/ScriptField"; + + +function makeTemplate(doc: Doc): boolean { + let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; + let layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0]; + let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, ""); + let docs = DocListCast(layoutDoc[fieldKey]); + let any = false; + docs.map(d => { + if (!StrCast(d.title).startsWith("-")) { + any = true; + return Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)); + } + if (d.type === DocumentType.COL) return makeTemplate(d); + return false; + }); + return any; +} +export function convertDropDataToButtons(data: DragManager.DocumentDragData) { + data && data.draggedDocuments.map((doc, i) => { + let dbox = doc; + if (!doc.onDragStart && !doc.onClick && doc.viewType !== CollectionViewType.Linear) { + let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; + if (layoutDoc.type === DocumentType.COL) { + layoutDoc.isTemplateDoc = makeTemplate(layoutDoc); + } else { + layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && !data.userDropAction; + } + dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); + dbox.dragFactory = layoutDoc; + dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; + dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); + } else if (doc.viewType === CollectionViewType.Linear) { + dbox.ignoreClick = true; + } + data.droppedDocuments[i] = dbox; + }); +} diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 04e131135..e8ef20899 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -47,46 +47,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } } - makeTemplate = (doc: Doc): boolean => { - let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; - let layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0]; - let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, ""); - let docs = DocListCast(layoutDoc[fieldKey]); - let any = false; - docs.map(d => { - if (!StrCast(d.title).startsWith("-")) { - any = true; - return Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)); - } - if (d.type === DocumentType.COL) return this.makeTemplate(d); - return false; - }); - return any; - } - - drop = action((e: Event, de: DragManager.DropEvent) => { - (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { - let dbox = doc; - if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { - let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; - if (layoutDoc.type === DocumentType.COL) { - layoutDoc.isTemplateDoc = this.makeTemplate(layoutDoc); - } else { - layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; - } - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); - dbox.dragFactory = layoutDoc; - dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; - dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); - } else if (doc.viewType === CollectionViewType.Linear) { - dbox.ignoreClick = true; - } - (de.data as DragManager.DocumentDragData).droppedDocuments[i] = dbox; - }); - e.stopPropagation(); - return super.drop(e, de); - }); - public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } dimension = () => NumCast(this.props.Document.height); // 2 * the padding diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e54374ad7..f9f040c6b 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -65,7 +65,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); let style = this.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : this.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` }; return
- {this.getDisplayDoc(pair.layout as Doc, pair.data, dxf, width)} + {pair.layout instanceof Doc && this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
; }); } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 1f8c0b18f..bc61492d0 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -6,7 +6,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast } from "../../../new_fields/Types"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from "../../../Utils"; @@ -23,6 +23,8 @@ import React = require("react"); var path = require('path'); import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; +import { CollectionViewType } from "./CollectionBaseView"; +import { ObjectField } from "../../../new_fields/ObjectField"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc) => boolean; @@ -126,6 +128,8 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { + (this.props.Document.dropConverter instanceof ScriptField) && + this.props.Document.dropConverter.script.run({ dragData: de.data }); if (de.data instanceof DragManager.DocumentDragData && !de.data.applyAsTemplate) { if (de.mods === "AltKey" && de.data.draggedDocuments.length) { this.childDocs.map(doc => diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index bff8ce5c1..7d0c900a6 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -10,7 +10,6 @@ width:100%; position: relative; top:0; - padding-top: 20px; padding-left: 10px; padding-right: 10px; background: $light-color-secondary; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2c77c4b5b..5d88e6290 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -576,7 +576,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); return !this.childDocs ? (null) : (
this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} onDrop={this.onTreeDrop} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index c5bc2e7fb..962fe2a1c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -31,7 +31,7 @@ export class CollectionFreeFormLinkView extends React.Component(["dropAction"]), dragFactory: data.dragFactory + backgroundColor: data.backgroundColor, removeDropProperties: new List(["dropAction"]), dragFactory: data.dragFactory, })); } @@ -69,7 +70,8 @@ export class CurrentUserUtils { static setupCreatePanel(sidebarContainer: Doc, doc: Doc) { // setup a masonry view of all he creators const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), { - width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons" + width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons", + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), yMargin: 0 }); // setup a color picker const color = Docs.Create.ColorDocument({ @@ -81,7 +83,7 @@ export class CurrentUserUtils { panel: Docs.Create.StackingDocument([dragCreators, color], { width: 500, height: 800, chromeStatus: "disabled", title: "creator stack" }), - onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel") + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel"), }); } @@ -148,7 +150,8 @@ export class CurrentUserUtils { doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], { title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", - backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, convertToButtons: true, + backgroundColor: "black", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, + dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) }); } -- cgit v1.2.3-70-g09d2 From 891b9706ddabc0a73ea6b25dc504297d6efb90fe Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 21 Oct 2019 22:03:02 -0400 Subject: big cleanup of layoutStrings, fieldExt, fieldKey, etc --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/documents/Documents.ts | 55 ++++---- src/client/northstar/dash-nodes/HistogramBox.tsx | 2 +- .../util/Import & Export/DirectoryImportBox.tsx | 2 +- src/client/views/CollectionLinearView.tsx | 4 +- src/client/views/DocComponent.tsx | 45 +++---- src/client/views/DocumentDecorations.tsx | 13 +- src/client/views/MainView.tsx | 5 +- .../views/collections/CollectionSchemaView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 2 - src/client/views/collections/CollectionSubView.tsx | 9 +- src/client/views/collections/CollectionView.tsx | 18 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++-- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 8 +- src/client/views/nodes/ButtonBox.tsx | 20 +-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 18 +-- src/client/views/nodes/ColorBox.tsx | 10 +- src/client/views/nodes/DocuLinkBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 71 ++-------- src/client/views/nodes/FieldView.tsx | 5 +- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 27 ++-- src/client/views/nodes/IconBox.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 18 +-- src/client/views/nodes/KeyValueBox.tsx | 5 +- src/client/views/nodes/PDFBox.tsx | 8 +- src/client/views/nodes/PresBox.tsx | 2 +- src/client/views/nodes/QueryBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 40 +++--- src/client/views/nodes/WebBox.tsx | 9 +- src/client/views/pdf/PDFViewer.tsx | 17 ++- .../views/presentationview/PresElementBox.tsx | 148 ++++++++++----------- src/new_fields/Doc.ts | 3 +- src/new_fields/documentSchemas.ts | 51 +++++++ 36 files changed, 319 insertions(+), 348 deletions(-) create mode 100644 src/new_fields/documentSchemas.ts (limited to 'src/client/util') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index d73988bb8..bed812852 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -40,7 +40,7 @@ export class YoutubeBox extends React.Component { @observable curVideoTemplates: VideoTemplate[] = []; - public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(YoutubeBox, fieldKey); } /** * When component mounts, last search's results are laoded in based on the back up stored diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 937d3c058..a400e68a3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -84,7 +84,8 @@ export interface DocumentOptions { columnWidth?: number; fontSize?: number; curPage?: number; - currentTimecode?: number; + currentTimecode?: number; // the current timecode of a time-based document (e.g., current time of a video) + displayTimecode?: number; // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) documentText?: string; borderRounding?: string; boxShadow?: string; @@ -119,11 +120,11 @@ export namespace Docs { export namespace Prototypes { - type LayoutSource = { LayoutString: (ext?: string) => string }; + type LayoutSource = { LayoutString: (key: string) => string }; type PrototypeTemplate = { layout: { view: LayoutSource, - ext?: string, // optional extension field for layout source + dataField: string }, options?: Partial }; @@ -133,80 +134,80 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.TEXT, { - layout: { view: FormattedTextBox }, + layout: { view: FormattedTextBox, dataField: data }, options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" } }], [DocumentType.HIST, { - layout: { view: HistogramBox }, + layout: { view: HistogramBox, dataField: data }, options: { height: 300, backgroundColor: "black" } }], [DocumentType.QUERY, { - layout: { view: QueryBox }, + layout: { view: QueryBox, dataField: data }, options: { width: 400 } }], [DocumentType.COLOR, { - layout: { view: ColorBox }, + layout: { view: ColorBox, dataField: data }, options: { nativeWidth: 220, nativeHeight: 300 } }], [DocumentType.IMG, { - layout: { view: ImageBox }, + layout: { view: ImageBox, dataField: data }, options: {} }], [DocumentType.WEB, { - layout: { view: WebBox }, + layout: { view: WebBox, dataField: data }, options: { height: 300 } }], [DocumentType.COL, { - layout: { view: CollectionView }, + layout: { view: CollectionView, dataField: data }, options: { panX: 0, panY: 0, scale: 1, width: 500, height: 500 } }], [DocumentType.KVP, { - layout: { view: KeyValueBox }, + layout: { view: KeyValueBox, dataField: data }, options: { height: 150 } }], [DocumentType.VID, { - layout: { view: VideoBox }, + layout: { view: VideoBox, dataField: data }, options: { currentTimecode: 0 }, }], [DocumentType.AUDIO, { - layout: { view: AudioBox }, + layout: { view: AudioBox, dataField: data }, options: { height: 32 } }], [DocumentType.PDF, { - layout: { view: PDFBox }, + layout: { view: PDFBox, dataField: data }, options: { nativeWidth: 1200, curPage: 1 } }], [DocumentType.ICON, { - layout: { view: IconBox }, + layout: { view: IconBox, dataField: data }, options: { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) }, }], [DocumentType.IMPORT, { - layout: { view: DirectoryImportBox }, + layout: { view: DirectoryImportBox, dataField: data }, options: { height: 150 } }], [DocumentType.LINKDOC, { data: new List(), - layout: { view: EmptyBox }, + layout: { view: EmptyBox, dataField: data }, }], [DocumentType.YOUTUBE, { - layout: { view: YoutubeBox } + layout: { view: YoutubeBox, dataField: data } }], [DocumentType.BUTTON, { - layout: { view: ButtonBox }, + layout: { view: ButtonBox, dataField: data }, }], [DocumentType.PRES, { - layout: { view: PresBox }, + layout: { view: PresBox, dataField: data }, options: {} }], [DocumentType.FONTICON, { - layout: { view: FontIconBox }, + layout: { view: FontIconBox, dataField: data }, options: { width: 40, height: 40, borderRounding: "100%" }, }], [DocumentType.LINKFOLLOW, { - layout: { view: LinkFollowBox } + layout: { view: LinkFollowBox, dataField: data } }], [DocumentType.PRESELEMENT, { - layout: { view: PresElementBox } + layout: { view: PresElementBox, dataField: data } }], ]); @@ -285,7 +286,7 @@ export namespace Docs { // synthesize the default options, the type and title from computed values and // whatever options pertain to this specific prototype let options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) }; - options.layout = layout.view.LayoutString(); + options.layout = layout.view.LayoutString(layout.dataField); return Doc.assign(new Doc(prototypeId, true), { ...options, baseLayout: options.layout }); } @@ -701,12 +702,12 @@ export namespace DocUtils { linkDocProto.linkDescription = description; linkDocProto.anchor1 = source.doc; - linkDocProto.anchor1Context = source.ctx; - linkDocProto.anchor1Timecode = source.doc.currentTimecode; - linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2 = target.doc; + linkDocProto.anchor1Context = source.ctx; linkDocProto.anchor2Context = target.ctx; + linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2Groups = new List([]); + linkDocProto.anchor1Timecode = source.doc.currentTimecode; linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index b81eafbee..854135648 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -24,7 +24,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; @observer export class HistogramBox extends React.Component { - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(HistogramBox, fieldStr); } + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(HistogramBox, fieldStr); } private _dropXRef = React.createRef(); private _dropYRef = React.createRef(); private _dropXDisposer?: DragManager.DragDropDisposer; diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index d74b51993..f27d05487 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -50,7 +50,7 @@ export default class DirectoryImportBox extends React.Component @observable private uploading = false; @observable private removeHover = false; - public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } constructor(props: FieldViewProps) { super(props); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 4e03b8c95..1f28ef35d 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -10,7 +10,8 @@ import { Transform } from '../util/Transform'; import "./CollectionLinearView.scss"; import { CollectionViewType } from './collections/CollectionBaseView'; import { CollectionSubView } from './collections/CollectionSubView'; -import { documentSchema, DocumentView } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; +import { documentSchema } from '../../new_fields/documentSchemas'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -75,7 +76,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed - get Document(): T { - return schemaCtor(this.props.Document); - } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } - @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); } } return Component; } - -/// DocStaticProps return a base class for React views of document fields that are interactive only when selected (e.g. ColorBox) -interface DocStaticProps { +/// DocStaticProps return a base class for React document views that have data extensions but aren't annotatable (e.g. AudioBox, FormattedTextBox) +interface DocExtendableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; isSelected: () => boolean; renderDepth: number; } -export function DocStaticComponent

(schemaCtor: (doc: Doc) => T) { +export function DocExtendableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed - get Document(): T { - return schemaCtor(this.props.Document); - } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } + @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools } @@ -59,23 +51,20 @@ interface DocAnnotatableProps { isSelected: () => boolean; renderDepth: number; } -export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T, fieldExt: string) { +export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ _isChildActive = false; - _fieldExt = fieldExt; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed - get Document(): T { - return schemaCtor(this.props.Document); - } + @computed get Document(): T { return schemaCtor(this.props.Document); } + @computed get layoutDoc() { return Doc.Layout(this.props.Document); } @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get fieldExt() { return this._fieldExt; } + @computed get annotationsKey() { return "annotations"; } @action.bound removeDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = undefined; - let value = this.extensionDoc && Cast(this.extensionDoc[this._fieldExt], listSpec(Doc), []); + let value = this.extensionDoc && Cast(this.extensionDoc[this.annotationsKey], listSpec(Doc), []); let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1; return index !== -1 && value && value.splice(index, 1) ? true : false; } @@ -88,7 +77,7 @@ export function DocAnnotatableComponent

(schema @action.bound addDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = this.props.Document; - return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this._fieldExt, doc) ? true : false; + return this.extensionDoc && Doc.AddDocToList(this.extensionDoc, this.annotationsKey, doc) ? true : false; } whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 252f90d46..b46caf3ea 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -4,27 +4,26 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; +import { PositionDocument } from '../../new_fields/documentSchemas'; import { List } from "../../new_fields/List"; import { ObjectField } from '../../new_fields/ObjectField'; -import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../new_fields/Types"; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { emptyFunction, Utils } from "../../Utils"; +import { Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import { DocumentManager } from "../util/DocumentManager"; import { DragManager } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; +import { TooltipTextMenu } from '../util/TooltipTextMenu'; import { undoBatch, UndoManager } from "../util/UndoManager"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { CollectionView } from "./collections/CollectionView"; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; -import { PositionDocument } from './nodes/CollectionFreeFormDocumentView'; import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; -import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); -import { TooltipTextMenu } from '../util/TooltipTextMenu'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -281,7 +280,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd); if (selectedDocs.length > 1) { - this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString()); + this._iconDoc = this._iconDoc ? this._iconDoc : this.createIcon(SelectionManager.SelectedDocuments(), CollectionView.LayoutString("")); this.moveIconDoc(this._iconDoc); } else { this.getIconDoc(selectedDocs[0]).then(icon => icon && this.moveIconDoc(this._iconDoc = icon)); @@ -339,7 +338,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc); if (!iconDoc || !DocumentManager.Instance.getDocumentView(iconDoc)) { - const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView)); + const layout = StrCast(doc.layout, FieldView.LayoutString(DocumentView, "")); iconDoc = this.createIcon([docView], layout); } return iconDoc; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index d4b92a110..55a61f098 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -270,7 +270,6 @@ export class MainView extends React.Component { doc) { this._heightMap.set(key, sectionHeight); } - get layoutDoc() { return Doc.Layout(this.props.Document); } - get Sections() { if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7f16fe9e0..55365de8c 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -41,7 +41,7 @@ export interface SubCollectionViewProps extends CollectionViewProps { ruleProvider: Doc | undefined; children?: never | (() => JSX.Element[]) | React.ReactNode; isAnnotationOverlay?: boolean; - fieldExt: string; + annotationsKey: string; } export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @@ -74,12 +74,15 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { this._childLayoutDisposer && this._childLayoutDisposer(); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); } + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through // to its children which may be templates. - // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. + // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { - return this.props.fieldExt ? (this.extensionDoc ? this.extensionDoc[this.props.fieldExt] : undefined) : this.dataDoc[this.props.fieldKey]; + return this.props.annotationsKey ? (this.extensionDoc ? this.extensionDoc[this.props.annotationsKey] : undefined) : this.dataDoc[this.props.fieldKey]; } get childLayoutPairs() { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f3b0b17f8..f8eb28ade 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -32,7 +32,7 @@ library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faF @observer export class CollectionView extends React.Component { - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(CollectionView, fieldStr); } + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _reactionDisposer: IReactionDisposer | undefined; @observable private _isLightboxOpen = false; @@ -63,18 +63,18 @@ export class CollectionView extends React.Component { private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { let props = { ...this.props, ...renderProps }; switch (type) { - case CollectionViewType.Schema: return (); + case CollectionViewType.Schema: return (); // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - case CollectionViewType.Docking: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } - case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } - case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } - case CollectionViewType.Linear: { return (); } + case CollectionViewType.Docking: return (); + case CollectionViewType.Tree: return (); + case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } + case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } + case CollectionViewType.Linear: { return (); } case CollectionViewType.Freeform: default: this.props.Document.freeformLayoutEngine = undefined; - return (); + return (); } return (null); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cc89dc2d4..0419bc3fa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -25,8 +25,8 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; -import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import { CollectionSubView } from "../CollectionSubView"; @@ -35,6 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { documentSchema, positionSchema } from "../../DocComponent"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -677,13 +678,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey. // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document - return ( + return !this.extensionDoc ? (null) :

- + {!this.extensionDoc ? (null) : @@ -694,8 +694,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { {this.overlayViews} -
- ); +
; } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3eaad3ad7..e0bf4a2f0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -19,23 +19,24 @@ import { CollectionViewType } from "../CollectionBaseView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); +import { SubCollectionViewProps } from "../CollectionSubView"; interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; - container: CollectionFreeFormView; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; selectDocuments: (docs: Doc[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; + extensionDoc: Doc; isAnnotationOverlay?: boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @observer -export class MarqueeView extends React.Component +export class MarqueeView extends React.Component { private _mainCont = React.createRef(); @observable _lastX: number = 0; @@ -187,13 +188,13 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + if (!this.props.active()) this.props.selectDocuments([this.props.Document]); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + this.props.selectDocuments(mselect.length ? mselect : [this.props.Document]); } this.cleanupInteractions(true); @@ -246,12 +247,11 @@ export class MarqueeView extends React.Component } get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. - return this.props.container.extensionDoc && Cast(this.props.container.extensionDoc.ink, InkField); + return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField); } set ink(value: InkField | undefined) { - let cprops = this.props.container.props; - this.props.container.extensionDoc && (this.props.container.extensionDoc.ink = value); + this.props.extensionDoc && (this.props.extensionDoc.ink = value); } @undoBatch @@ -290,11 +290,11 @@ export class MarqueeView extends React.Component } let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) this.props.container.props.Document.colorPalette = new List(defaultPalette); - let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); + let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.Document.colorPalette = new List(defaultPalette); + let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map(); - [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { + [...this.props.activeDocuments(), this.props.Document].map(child => { let bg = StrCast(Doc.Layout(child).backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 32ebe7c61..ef194624a 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -37,7 +37,7 @@ enum FollowOptions { @observer export class LinkFollowBox extends React.Component { - public static LayoutString() { return FieldView.LayoutString(LinkFollowBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); } public static Instance: LinkFollowBox | undefined; @observable static linkDoc: Doc | undefined = undefined; @observable static destinationDoc: Doc | undefined = undefined; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 3e5deb55b..4c1c3a465 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -4,18 +4,18 @@ import { observer } from "mobx-react"; import "./AudioBox.scss"; import { Cast } from "../../../new_fields/Types"; import { AudioField } from "../../../new_fields/URLField"; -import { DocStaticComponent } from "../DocComponent"; +import { DocExtendableComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -import { documentSchema } from "./DocumentView"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type AudioDocument = makeInterface<[typeof documentSchema]>; const AudioDocument = makeInterface(documentSchema); const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3")); @observer -export class AudioBox extends DocStaticComponent(AudioDocument) { +export class AudioBox extends DocExtendableComponent(AudioDocument) { - public static LayoutString() { return FieldView.LayoutString(AudioBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } _ref = React.createRef(); componentDidMount() { diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index b4d33fb0f..1531d825b 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -3,11 +3,11 @@ import { faEdit } from '@fortawesome/free-regular-svg-icons'; import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCastAsync, DocListCast } from '../../../new_fields/Doc'; +import { Doc, DocListCast } from '../../../new_fields/Doc'; import { List } from '../../../new_fields/List'; import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema'; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, StrCast, Cast } from '../../../new_fields/Types'; +import { BoolCast, StrCast, Cast, FieldValue } from '../../../new_fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { DocComponent } from '../DocComponent'; @@ -15,26 +15,28 @@ import './ButtonBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; +import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit as any); const ButtonSchema = createSchema({ onClick: ScriptField, + buttonParams: listSpec("string"), text: "string" }); -type ButtonDocument = makeInterface<[typeof ButtonSchema]>; -const ButtonDocument = makeInterface(ButtonSchema); +type ButtonDocument = makeInterface<[typeof ButtonSchema, typeof documentSchema]>; +const ButtonDocument = makeInterface(ButtonSchema, documentSchema); @observer export class ButtonBox extends DocComponent(ButtonDocument) { - public static LayoutString() { return FieldView.LayoutString(ButtonBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ButtonBox, fieldKey); } private dropDisposer?: DragManager.DragDropDisposer; @computed get dataDoc() { return this.props.DataDoc && - (BoolCast(this.props.Document.isTemplateField) || BoolCast(this.props.DataDoc.isTemplateField) || + (this.Document.isTemplateField || BoolCast(this.props.DataDoc.isTemplateField) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } @@ -52,7 +54,7 @@ export class ButtonBox extends DocComponent(Butt let funcs: ContextMenuProps[] = []; funcs.push({ description: "Clear Script Params", event: () => { - let params = Cast(this.props.Document.buttonParams, listSpec("string")); + let params = FieldValue(this.Document.buttonParams); params && params.map(p => this.props.Document[p] = undefined); }, icon: "trash" }); @@ -70,13 +72,13 @@ export class ButtonBox extends DocComponent(Butt } // (!missingParams || !missingParams.length ? "" : "(" + missingParams.map(m => m + ":").join(" ") + ")") render() { - let params = Cast(this.props.Document.buttonParams, listSpec("string")); + let params = this.Document.buttonParams; let missingParams = params && params.filter(p => this.props.Document[p] === undefined); params && params.map(p => DocListCast(this.props.Document[p])); // bcz: really hacky form of prefetching ... return (
-
+
{(this.Document.text || this.Document.title)}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index e3ca02fa4..58cb831f8 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -2,14 +2,15 @@ import { random } from "animejs"; import { computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { listSpec } from "../../../new_fields/Schema"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { percent2frac } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; import "./CollectionFreeFormDocumentView.scss"; -import { documentSchema, DocumentView, DocumentViewProps } from "./DocumentView"; +import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); +import { PositionDocument } from "../../../new_fields/documentSchemas"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; @@ -20,15 +21,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { jitterRotation: number; transition?: string; } -export const positionSchema = createSchema({ - zIndex: "number", - x: "number", - y: "number", - z: "number", -}); - -export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; -export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { @@ -92,8 +84,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; - get layoutDoc() { return Doc.Layout(this.props.Document); } - @observable _animPos: number[] | undefined = undefined; finalPanelWidth = () => this.dataProvider ? this.dataProvider.width : this.panelWidth(); diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index fdcedb3a5..fda6d64f4 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -4,20 +4,20 @@ import { SketchPicker } from 'react-color'; import { FieldView, FieldViewProps } from './FieldView'; import "./ColorBox.scss"; import { InkingControl } from "../InkingControl"; -import { DocStaticComponent } from "../DocComponent"; -import { documentSchema } from "./DocumentView"; +import { DocExtendableComponent } from "../DocComponent"; import { makeInterface } from "../../../new_fields/Schema"; -import { trace, reaction, observable, action, IReactionDisposer } from "mobx"; +import { reaction, observable, action, IReactionDisposer } from "mobx"; import { SelectionManager } from "../../util/SelectionManager"; import { StrCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type ColorDocument = makeInterface<[typeof documentSchema]>; const ColorDocument = makeInterface(documentSchema); @observer -export class ColorBox extends DocStaticComponent(ColorDocument) { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); } +export class ColorBox extends DocExtendableComponent(ColorDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ColorBox, fieldKey); } _selectedDisposer: IReactionDisposer | undefined; _penDisposer: IReactionDisposer | undefined; diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 22e44948a..7119b0db0 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -7,11 +7,11 @@ import { Utils } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragLinksAsDocuments } from "../../util/DragManager"; import { DocComponent } from "../DocComponent"; -import { documentSchema } from "./DocumentView"; import "./DocuLinkBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; import React = require("react"); import { DocumentType } from "../../documents/DocumentTypes"; +import { documentSchema } from "../../../new_fields/documentSchemas"; type DocLinkSchema = makeInterface<[typeof documentSchema]>; const DocLinkDocument = makeInterface(documentSchema); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9e36f0811..f741dae9a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,29 +1,37 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import * as fa from '@fortawesome/free-solid-svg-icons'; -import { action, computed, runInAction, trace, observable } from "mobx"; +import { action, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as rp from "request-promise"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc"; +import { Document } from '../../../new_fields/documentSchemas'; import { Id } from '../../../new_fields/FieldSymbols'; -import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema"; +import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { BoolCast, Cast, NumCast, PromiseValue, StrCast, FieldValue } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { ImageField } from '../../../new_fields/URLField'; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; -import { emptyFunction, returnTrue, Utils, returnTransparent, returnOne } from "../../../Utils"; +import { emptyFunction, returnTransparent, returnTrue, Utils } from "../../../Utils"; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; +import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; import { LinkManager } from '../../util/LinkManager'; +import { Scripting } from '../../util/Scripting'; import { SelectionManager } from "../../util/SelectionManager"; +import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { CollectionViewType } from '../collections/CollectionBaseView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; +import { DictationOverlay } from '../DictationOverlay'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { OverlayView } from '../OverlayView'; @@ -33,13 +41,6 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { DocumentType } from '../../documents/DocumentTypes'; -import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; -import { ImageField } from '../../../new_fields/URLField'; -import SharingManager from '../../util/SharingManager'; -import { Scripting } from '../../util/Scripting'; -import { DictationOverlay } from '../DictationOverlay'; -import { CollectionViewType } from '../collections/CollectionBaseView'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -67,7 +68,6 @@ library.add(fa.faLaptopCode, fa.faMale, fa.faCopy, fa.faHandPointRight, fa.faCom export interface DocumentViewProps { ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; - fieldKey: string; Document: Doc; DataDoc?: Doc; fitToBox?: boolean; @@ -96,41 +96,6 @@ export interface DocumentViewProps { layoutKey?: string; } -export const documentSchema = createSchema({ - // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out - title: "string", // document title (can be on either data document or layout) - nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set - nativeHeight: "number", // " - width: "number", // width of document in its container's coordinate system - height: "number", // " - backgroundColor: "string", // background color of document - opacity: "number", // opacity of document - //links: listSpec(Doc), // computed (readonly) list of links associated with this document - dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") - removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped - onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) - onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. - ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document - autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents - isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed - isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) - type: "string", // enumerated type of document - maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) - lockedPosition: "boolean", // whether the document can be spatially manipulated - inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently - borderRounding: "string", // border radius rounding of document - searchFields: "string", // the search fields to display when this document matches a search in its metadata - heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) - showCaption: "string", // whether editable caption text is overlayed at the bottom of the document - showTitle: "string", // whether an editable title banner is displayed at tht top of the document - isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) - ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) -}); - - -type Document = makeInterface<[typeof documentSchema]>; -const Document = makeInterface(documentSchema); @observer export class DocumentView extends DocComponent(Document) { @@ -572,13 +537,6 @@ export class DocumentView extends DocComponent(Docu }); } - - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field. In that case, all layout information comes from there unless overriden by Document - get layoutDoc(): Document { - return Document(Doc.Layout(this.props.Document)); - } - // does Document set a layout prop setsLayoutProp = (prop: string) => this.props.Document[prop] !== this.props.Document["default" + prop[0].toUpperCase() + prop.slice(1)]; // get the a layout prop by first choosing the prop from Document, then falling back to the layout doc otherwise. @@ -599,7 +557,6 @@ export class DocumentView extends DocComponent(Docu return ((Docu } } -Scripting.addGlobal(function toggleDetail(doc: any) { - doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; -}); \ No newline at end of file +Scripting.addGlobal(function toggleDetail(doc: any) { doc.layoutKey = StrCast(doc.layoutKey, "layout") === "layout" ? "layoutCustom" : "layout"; }); \ No newline at end of file diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 7d69b5b51..5108954bb 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -52,9 +52,8 @@ export interface FieldViewProps { @observer export class FieldView extends React.Component { - public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") { - return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; - //"" + public static LayoutString(fieldType: { name: string }, fieldStr: string) { + return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"}/>`; //e.g., "" } @computed diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index fd6a475fb..ae9273639 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -17,7 +17,7 @@ type FontIconDocument = makeInterface<[typeof FontIconSchema]>; const FontIconDocument = makeInterface(FontIconSchema); @observer export class FontIconBox extends DocComponent(FontIconDocument) { - public static LayoutString() { return FieldView.LayoutString(FontIconBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } @observable _foregroundColor = "white"; _ref: React.RefObject = React.createRef(); _backgroundReaction: IReactionDisposer | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 995fcee17..5c2d39d98 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -33,7 +33,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocComponent } from "../DocComponent"; +import { DocExtendableComponent } from "../DocComponent"; import { DocumentButtonBar } from '../DocumentButtonBar'; import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; @@ -44,6 +44,7 @@ import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { documentSchema } from '../../../new_fields/documentSchemas'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -62,16 +63,14 @@ const richTextSchema = createSchema({ export const GoogleRef = "googleDocId"; -type RichTextDocument = makeInterface<[typeof richTextSchema]>; -const RichTextDocument = makeInterface(richTextSchema); +type RichTextDocument = makeInterface<[typeof richTextSchema, typeof documentSchema]>; +const RichTextDocument = makeInterface(richTextSchema, documentSchema); type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { - public static LayoutString(fieldStr: string = "data") { - return FieldView.LayoutString(FormattedTextBox, fieldStr); - } +export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; @@ -135,17 +134,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return true; } - constructor(props: FieldViewProps) { + constructor(props: any) { super(props); FormattedTextBox.Instance = this; } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } - // the document containing the view layout information - will be the Document itself unless the Document has - // a layout field. In that case, all layout information comes from there unless overriden by Document - @computed get layoutDoc(): Doc { return Doc.Layout(this.props.Document); } - linkOnDeselect: Map = new Map(); doLinkOnDeselect() { @@ -266,8 +261,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe newLayout = Doc.MakeDelegate(draggedDoc); newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); } - this.props.Document.layoutCustom = newLayout; - this.props.Document.layoutKey = "layoutCustom"; + this.Document.layoutCustom = newLayout; + this.Document.layoutKey = "layoutCustom"; e.stopPropagation(); // embed document when dragging with a userDropAction or an embedDoc flag set } else if (de.data.userDropAction || de.data.embedDoc) { @@ -1015,7 +1010,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - let nh = this.props.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); + let nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.layoutDoc.height, 0); this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; @@ -1052,7 +1047,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
+
{ diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx index 4971f61b7..60f547b1e 100644 --- a/src/client/views/nodes/IconBox.tsx +++ b/src/client/views/nodes/IconBox.tsx @@ -24,7 +24,7 @@ library.add(faFilm, faTag, faTextHeight); @observer export class IconBox extends React.Component { - public static LayoutString() { return FieldView.LayoutString(IconBox); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(IconBox, fieldKey); } @observable _panelWidth: number = 0; @observable _panelHeight: number = 0; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4d623a04f..4c2f8e22d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -20,12 +20,12 @@ import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from '../DocComponent'; import { InkingControl } from '../InkingControl'; -import { documentSchema } from './DocumentView'; import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { documentSchema } from '../../../new_fields/documentSchemas'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -38,7 +38,9 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", fitWidth: "boolean", - rotation: "number" + rotation: "number", + googlePhotosUrl: "string", + googlePhotosTags: "string" }); interface Window { @@ -54,8 +56,8 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); @observer -export class ImageBox extends DocAnnotatableComponent(ImageDocument, "annotations") { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(ImageBox, fieldKey); } +export class ImageBox extends DocAnnotatableComponent(ImageDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject = React.createRef(); private _dropDisposer?: DragManager.DragDropDisposer; @observable private _audioState = 0; @@ -251,7 +253,7 @@ export class ImageBox extends DocAnnotatableComponent this.recordAudioAnnotation(); considerGooglePhotosLink = () => { - const remoteUrl = StrCast(this.props.Document.googlePhotosUrl); + const remoteUrl = this.Document.googlePhotosUrl; return !remoteUrl ? (null) : ( { - const tags = StrCast(this.props.Document.googlePhotosTags); + const tags = this.Document.googlePhotosTags; return !tags ? (null) : (); } @@ -287,7 +289,7 @@ export class ImageBox extends DocAnnotatableComponent { + public static LayoutString(fieldStr: string) { return FieldView.LayoutString(KeyValueBox, fieldStr); } + private _mainCont = React.createRef(); private _keyHeader = React.createRef(); - @observable private rows: KeyValuePair[] = []; - public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); } + @observable private rows: KeyValuePair[] = []; @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b1110daf4..396a5356a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -16,18 +16,18 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { DocAnnotatableComponent } from "../DocComponent"; import { PDFViewer } from "../pdf/PDFViewer"; -import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { documentSchema } from '../../../new_fields/documentSchemas'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer -export class PDFBox extends DocAnnotatableComponent(PdfDocument, "annotations") { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(PDFBox, fieldKey); } +export class PDFBox extends DocAnnotatableComponent(PdfDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; @@ -232,7 +232,7 @@ export class PDFBox extends DocAnnotatableComponent pinToPres={this.props.pinToPres} addDocument={this.addDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} - fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} /> + fieldKey={this.props.fieldKey} startupLive={this._initialScale < 2.5 ? true : false} /> {this.settingsPanel()}
); } diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 15fafb022..7ec5d0471 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -31,7 +31,7 @@ library.add(faEdit); @observer export class PresBox extends React.Component { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { this._docListChangedReaction = reaction(() => { diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx index ced597b59..99b5810fc 100644 --- a/src/client/views/nodes/QueryBox.tsx +++ b/src/client/views/nodes/QueryBox.tsx @@ -18,7 +18,7 @@ library.add(faEdit); @observer export class QueryBox extends React.Component { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(QueryBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } _docListChangedReaction: IReactionDisposer | undefined; componentDidMount() { } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 64871ef41..48a699e58 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,33 +1,32 @@ import React = require("react"); -import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from "mobx"; +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faVideo } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx"; import { observer } from "mobx-react"; import * as rp from 'request-promise'; +import { Doc } from "../../../new_fields/Doc"; import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, StrCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; -import { Utils, emptyFunction, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, Utils } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { DocAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; -import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { faVideo } from "@fortawesome/free-solid-svg-icons"; -import { Doc } from "../../../new_fields/Doc"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { positionSchema } from "./CollectionFreeFormDocumentView"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; +import { documentSchema, positionSchema } from "../../../new_fields/documentSchemas"; var path = require('path'); export const timeSchema = createSchema({ - currentTimecode: "number", + currentTimecode: "number", // the current time of a video or other linear, time-based document. Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first }); type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>; const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); @@ -35,7 +34,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); library.add(faVideo); @observer -export class VideoBox extends DocAnnotatableComponent(VideoDocument, "annotations") { +export class VideoBox extends DocAnnotatableComponent(VideoDocument) { static _youtubeIframeCounter: number = 0; private _reactionDisposer?: IReactionDisposer; private _youtubeReactionDisposer?: IReactionDisposer; @@ -49,7 +48,7 @@ export class VideoBox extends DocAnnotatableComponent this._playing ? this.Pause() : this.Play() + onPlayDown = () => this._playing ? this.Pause() : this.Play(); - @action onFullDown = (e: React.PointerEvent) => { this.FullScreen(); e.stopPropagation(); e.preventDefault(); } - @action onSnapshot = (e: React.PointerEvent) => { this.Snapshot(); e.stopPropagation(); e.preventDefault(); } - @action onResetDown = (e: React.PointerEvent) => { this.Pause(); e.stopPropagation(); @@ -309,12 +304,12 @@ export class VideoBox extends DocAnnotatableComponent { this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY); this.Seek(Math.max(0, (this.Document.currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); e.stopImmediatePropagation(); } + @action onResetUp = (e: PointerEvent) => { document.removeEventListener("pointermove", this.onResetMove, true); @@ -322,7 +317,6 @@ export class VideoBox extends DocAnnotatableComponent; const WebDocument = makeInterface(documentSchema); @observer -export class WebBox extends DocAnnotatableComponent(WebDocument, "annotations") { +export class WebBox extends DocAnnotatableComponent(WebDocument) { - public static LayoutString(fieldKey: string = "data") { return FieldView.LayoutString(WebBox, fieldKey); } + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @observable private collapsed: boolean = true; @observable private url: string = ""; - get layoutDoc() { return Doc.Layout(this.props.Document); } componentWillMount() { let field = Cast(this.props.Document[this.props.fieldKey], WebField); @@ -199,7 +198,7 @@ export class WebBox extends DocAnnotatableComponent ; - extensionDoc: Doc; PanelWidth: () => number; PanelHeight: () => number; ContentScaling: () => number; @@ -73,7 +72,7 @@ interface IViewerProps { * Handles rendering and virtualization of the pdf */ @observer -export class PDFViewer extends DocAnnotatableComponent(PdfDocument, "annotations") { +export class PDFViewer extends DocAnnotatableComponent(PdfDocument) { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @@ -109,8 +108,8 @@ export class PDFViewer extends DocAnnotatableComponent this._script.run({ this: anno }, console.log, true).result); + return this.extensionDoc ? DocListCast(this.extensionDoc.annotations).filter( + anno => this._script.run({ this: anno }, console.log, true).result) : []; } @computed get nonDocAnnotations() { @@ -200,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations), + () => this.extensionDoc && DocListCast(this.extensionDoc.annotations), annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), { fireImmediately: true }); @@ -629,10 +628,10 @@ export class PDFViewer extends DocAnnotatableComponent {this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => - )} + )}
(this.Document.scrollHeight || this.Document.nativeHeight || 0)} PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : (this.Document.nativeWidth || 0)} @@ -673,7 +672,7 @@ export class PDFViewer extends DocAnnotatableComponent this._marqueeing; visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; render() { - return (
{this.pdfViewerDiv} {this.annotationLayer} diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 0824808eb..5f758f496 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -5,18 +5,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { documentSchema } from '../../../new_fields/documentSchemas'; import { Id } from "../../../new_fields/FieldSymbols"; -import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, returnOne } from "../../../Utils"; +import { createSchema, makeInterface } from '../../../new_fields/Schema'; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction, returnFalse } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; import { CollectionViewType } from '../collections/CollectionBaseView'; -import { DocumentView } from "../nodes/DocumentView"; +import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; +import { DocComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); -import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; - library.add(faArrowUp); library.add(fileSolid); @@ -24,34 +25,34 @@ library.add(faLocationArrow); library.add(fileRegular as any); library.add(faSearch); library.add(faArrowDown); + +export const presSchema = createSchema({ + presentationTargetDoc: Doc, + presBox: Doc, + presBoxKey: "string", + showButton: "boolean", + navButton: "boolean", + hideTillShownButton: "boolean", + fadeButton: "boolean", + hideAfterButton: "boolean", + groupButton: "boolean", + embedOpen: "boolean" +}); + +type PresDocument = makeInterface<[typeof presSchema, typeof documentSchema]>; +const PresDocument = makeInterface(presSchema, documentSchema); /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. */ @observer -export class PresElementBox extends React.Component { +export class PresElementBox extends DocComponent(PresDocument) { + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } - public static LayoutString() { return FieldView.LayoutString(PresElementBox); } - - @computed get myIndex() { return DocListCast(this.presentationDoc[this.presentationFieldKey]).indexOf(this.props.Document); } - @computed get presentationDoc() { return this.props.Document.presBox as Doc; } - @computed get presentationFieldKey() { return StrCast(this.props.Document.presBoxKey); } + @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); } + @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; } + @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc } @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); } - @computed get showButton() { return BoolCast(this.props.Document.showButton); } - @computed get navButton() { return BoolCast(this.props.Document.navButton); } - @computed get hideTillShownButton() { return BoolCast(this.props.Document.hideTillShownButton); } - @computed get fadeButton() { return BoolCast(this.props.Document.fadeButton); } - @computed get hideAfterButton() { return BoolCast(this.props.Document.hideAfterButton); } - @computed get groupButton() { return BoolCast(this.props.Document.groupButton); } - @computed get embedOpen() { return BoolCast(this.props.Document.embedOpen); } - - set embedOpen(value: boolean) { this.props.Document.embedOpen = value; } - set showButton(val: boolean) { this.props.Document.showButton = val; } - set navButton(val: boolean) { this.props.Document.navButton = val; } - set hideTillShownButton(val: boolean) { this.props.Document.hideTillShownButton = val; } - set fadeButton(val: boolean) { this.props.Document.fadeButton = val; } - set hideAfterButton(val: boolean) { this.props.Document.hideAfterButton = val; } - set groupButton(val: boolean) { this.props.Document.groupButton = val; } /** * The function that is called on click to turn Hiding document till press option on/off. @@ -60,14 +61,14 @@ export class PresElementBox extends React.Component { @action onHideDocumentUntilPressClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.hideTillShownButton = !this.hideTillShownButton; - if (!this.hideTillShownButton) { - if (this.myIndex >= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.hideTillShownButton = !this.Document.hideTillShownButton; + if (!this.Document.hideTillShownButton) { + if (this.indexInPres >= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - if (this.presentationDoc.presStatus && this.myIndex > this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0; + if (this.presentationDoc.presStatus && this.indexInPres > this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 0; } } } @@ -80,15 +81,15 @@ export class PresElementBox extends React.Component { @action onHideDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.hideAfterButton = !this.hideAfterButton; - if (!this.hideAfterButton) { - if (this.myIndex <= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.hideAfterButton = !this.Document.hideAfterButton; + if (!this.Document.hideAfterButton) { + if (this.indexInPres <= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - if (this.fadeButton) this.fadeButton = false; - if (this.presentationDoc.presStatus && this.myIndex < this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0; + if (this.Document.fadeButton) this.Document.fadeButton = false; + if (this.presentationDoc.presStatus && this.indexInPres < this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 0; } } } @@ -101,15 +102,15 @@ export class PresElementBox extends React.Component { @action onFadeDocumentAfterPresentedClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.fadeButton = !this.fadeButton; - if (!this.fadeButton) { - if (this.myIndex <= this.currentIndex) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 1; + this.Document.fadeButton = !this.Document.fadeButton; + if (!this.Document.fadeButton) { + if (this.indexInPres <= this.currentIndex && this.targetDoc) { + this.targetDoc.opacity = 1; } } else { - this.hideAfterButton = false; - if (this.presentationDoc.presStatus && (this.myIndex < this.currentIndex)) { - (this.props.Document.presentationTargetDoc as Doc).opacity = 0.5; + this.Document.hideAfterButton = false; + if (this.presentationDoc.presStatus && (this.indexInPres < this.currentIndex) && this.targetDoc) { + this.targetDoc.opacity = 0.5; } } } @@ -120,10 +121,10 @@ export class PresElementBox extends React.Component { @action onNavigateDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.navButton = !this.navButton; - if (this.navButton) { - this.showButton = false; - if (this.currentIndex === this.myIndex) { + this.Document.navButton = !this.Document.navButton; + if (this.Document.navButton) { + this.Document.showButton = false; + if (this.currentIndex === this.indexInPres) { this.props.focus(this.props.Document); } } @@ -136,12 +137,12 @@ export class PresElementBox extends React.Component { onZoomDocumentClick = (e: React.MouseEvent) => { e.stopPropagation(); - this.showButton = !this.showButton; - if (!this.showButton) { + this.Document.showButton = !this.Document.showButton; + if (!this.Document.showButton) { this.props.Document.viewScale = 1; } else { - this.navButton = false; - if (this.currentIndex === this.myIndex) { + this.Document.navButton = false; + if (this.currentIndex === this.indexInPres) { this.props.focus(this.props.Document); } } @@ -151,13 +152,12 @@ export class PresElementBox extends React.Component { */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; - get layoutDoc() { return Doc.Layout(this.props.Document); } /** * The function that is responsible for rendering the a preview or not for this * presentation element. */ renderEmbeddedInline = () => { - if (!this.embedOpen || !(this.props.Document.presentationTargetDoc instanceof Doc)) { + if (!this.Document.embedOpen || !this.targetDoc) { return (null); } @@ -170,8 +170,9 @@ export class PresElementBox extends React.Component { width: propDocWidth === 0 ? "auto" : propDocWidth * scale(), }}> { } render() { - let p = this.props; - let treecontainer = this.props.ContainingCollectionDoc && this.props.ContainingCollectionDoc.viewType === CollectionViewType.Tree; - let className = "presElementBox-item" + (this.currentIndex === this.myIndex ? " presElementBox-selected" : ""); + let className = "presElementBox-item" + (this.currentIndex === this.indexInPres ? " presElementBox-selected" : ""); let pbi = "presElementBox-interaction"; return ( -
{ p.focus(p.Document); e.stopPropagation(); }}> +
{ this.props.focus(this.props.Document); e.stopPropagation(); }}> {treecontainer ? (null) : <> - {`${this.myIndex + 1}. ${p.Document.title}`} + {`${this.indexInPres + 1}. ${this.Document.title}`} - +
- - } - - - - - - - + } + + + + + + +
{this.renderEmbeddedInline()} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index c6f654c33..08cb66d5f 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -645,7 +645,8 @@ export namespace Doc { @observable BrushedDoc: ObservableMap = new ObservableMap(); } - // returns the active layout document for 'doc'. + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document export function Layout(doc: Doc) { return Doc.LayoutField(doc) instanceof Doc ? doc[StrCast(doc.layoutKey, "layout")] as Doc : doc; } export function LayoutField(doc: Doc) { return doc[StrCast(doc.layoutKey, "layout")]; } const manager = new DocData(); diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts new file mode 100644 index 000000000..8c3b62067 --- /dev/null +++ b/src/new_fields/documentSchemas.ts @@ -0,0 +1,51 @@ +import { makeInterface, createSchema, listSpec } from "./Schema"; +import { ScriptField } from "./ScriptField"; +import { Doc } from "./Doc"; + +export const documentSchema = createSchema({ + // layout: "string", // this should be a "string" or Doc, but can't do that in schemas, so best to leave it out + layoutKey: "string", // holds the field key for the field that actually holds the current lyoat + layoutCustom: Doc, // used to hold a custom layout (there's nothing special about this field .. any field could hold a custom layout that can be selected by setting 'layoutKey') + title: "string", // document title (can be on either data document or layout) + nativeWidth: "number", // native width of document which determines how much document contents are scaled when the document's width is set + nativeHeight: "number", // " + width: "number", // width of document in its container's coordinate system + height: "number", // " + color: "string", // foreground color of document + backgroundColor: "string", // background color of document + opacity: "number", // opacity of document + //links: listSpec(Doc), // computed (readonly) list of links associated with this document + dropAction: "string", // override specifying what should happen when this document is dropped (can be "alias" or "copy") + removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped + onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) + onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. + ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document + autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents + isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed + isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) + type: "string", // enumerated type of document + maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) + lockedPosition: "boolean", // whether the document can be spatially manipulated + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently + borderRounding: "string", // border radius rounding of document + searchFields: "string", // the search fields to display when this document matches a search in its metadata + heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) + showCaption: "string", // whether editable caption text is overlayed at the bottom of the document + showTitle: "string", // whether an editable title banner is displayed at tht top of the document + isButton: "boolean", // whether document functions as a button (overiding native interactions of its content) + ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) +}); + +export const positionSchema = createSchema({ + zIndex: "number", + x: "number", + y: "number", + z: "number", +}); + +export type Document = makeInterface<[typeof documentSchema]>; +export const Document = makeInterface(documentSchema); + +export type PositionDocument = makeInterface<[typeof documentSchema, typeof positionSchema]>; +export const PositionDocument = makeInterface(documentSchema, positionSchema); -- cgit v1.2.3-70-g09d2 From 3b16a81e21942684d63f175b60629f85070715e1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 21 Oct 2019 23:55:19 -0400 Subject: got rid of CollectionBaseView --- src/client/documents/Documents.ts | 2 +- src/client/util/DictationManager.ts | 2 +- src/client/util/DropConverter.ts | 2 +- src/client/views/CollectionLinearView.tsx | 2 +- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionBaseView.scss | 26 --- .../views/collections/CollectionBaseView.tsx | 180 -------------------- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/CollectionView.scss | 26 +++ src/client/views/collections/CollectionView.tsx | 183 ++++++++++++++++----- .../views/collections/CollectionViewChromes.tsx | 2 +- .../views/collections/ParentDocumentSelector.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/ButtonBox.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/PresBox.tsx | 2 +- .../views/presentationview/PresElementBox.tsx | 4 +- src/client/views/search/SearchItem.tsx | 2 +- 20 files changed, 186 insertions(+), 266 deletions(-) delete mode 100644 src/client/views/collections/CollectionBaseView.scss delete mode 100644 src/client/views/collections/CollectionBaseView.tsx create mode 100644 src/client/views/collections/CollectionView.scss (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a400e68a3..b1406d5e1 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -2,7 +2,7 @@ import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; import { HistogramOperation } from "../northstar/operations/HistogramOperation"; import { CollectionView } from "../views/collections/CollectionView"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { AudioBox } from "../views/nodes/AudioBox"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { ImageBox } from "../views/nodes/ImageBox"; diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index ae991635f..6bbd3d0ed 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -6,7 +6,7 @@ import { DocumentType } from "../documents/DocumentTypes"; import { Doc, Opt } from "../../new_fields/Doc"; import { List } from "../../new_fields/List"; import { Docs } from "../documents/Documents"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { Cast, CastCtor } from "../../new_fields/Types"; import { listSpec } from "../../new_fields/Schema"; import { AudioField, ImageField } from "../../new_fields/URLField"; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index eea3da1bc..6b53333d7 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,5 +1,5 @@ import { DragManager } from "./DragManager"; -import { CollectionViewType } from "../views/collections/CollectionBaseView"; +import { CollectionViewType } from "../views/collections/CollectionView"; import { Doc, DocListCast } from "../../new_fields/Doc"; import { DocumentType } from "../documents/DocumentTypes"; import { ObjectField } from "../../new_fields/ObjectField"; diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 1f28ef35d..31a518a6c 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -8,7 +8,7 @@ import { emptyFunction, returnEmptyString, returnOne, returnTrue, Utils } from ' import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import "./CollectionLinearView.scss"; -import { CollectionViewType } from './collections/CollectionBaseView'; +import { CollectionViewType } from './collections/CollectionView'; import { CollectionSubView } from './collections/CollectionSubView'; import { DocumentView } from './nodes/DocumentView'; import { documentSchema } from '../../new_fields/documentSchemas'; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 55a61f098..26e70c5c7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -24,7 +24,7 @@ import { HistoryUtil } from '../util/History'; import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; import { CollectionLinearView } from './CollectionLinearView'; -import { CollectionBaseView, CollectionViewType } from './collections/CollectionBaseView'; +import { CollectionViewType, CollectionView } from './collections/CollectionView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DictationOverlay } from './DictationOverlay'; @@ -228,7 +228,7 @@ export class MainView extends React.Component { if (!state.nro) { DocServer.Control.makeReadOnly(); } - CollectionBaseView.SetSafeMode(true); + CollectionView.SetSafeMode(true); } else if (state.nro || state.nro === null || state.readonly === false) { } else if (doc.readOnly) { DocServer.Control.makeReadOnly(); diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss deleted file mode 100644 index aff965469..000000000 --- a/src/client/views/collections/CollectionBaseView.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "../globalCssVariables"; - -#collectionBaseView { - border-width: 0; - border-color: $light-color-secondary; - border-style: solid; - border-radius: 0 0 $border-radius $border-radius; - box-sizing: border-box; - border-radius: inherit; - width: 100%; - height: 100%; - overflow: auto; -} - -#google-tags { - transition: all 0.5s ease 0s; - width: 30px; - height: 30px; - position: absolute; - bottom: 15px; - left: 15px; - border: 2px solid black; - border-radius: 50%; - padding: 3px; - background: white; -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx deleted file mode 100644 index 9f3d33e6f..000000000 --- a/src/client/views/collections/CollectionBaseView.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { action, computed, observable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast } from '../../../new_fields/Doc'; -import { listSpec } from '../../../new_fields/Schema'; -import { BoolCast, Cast, FieldValue, PromiseValue, StrCast } from '../../../new_fields/Types'; -import { ImageField } from '../../../new_fields/URLField'; -import { DocumentManager } from '../../util/DocumentManager'; -import { SelectionManager } from '../../util/SelectionManager'; -import { ContextMenu } from '../ContextMenu'; -import { FieldViewProps } from '../nodes/FieldView'; -import './CollectionBaseView.scss'; - -export enum CollectionViewType { - Invalid, - Freeform, - Schema, - Docking, - Tree, - Stacking, - Masonry, - Pivot, - Linear, -} - -export namespace CollectionViewType { - - const stringMapping = new Map([ - ["invalid", CollectionViewType.Invalid], - ["freeform", CollectionViewType.Freeform], - ["schema", CollectionViewType.Schema], - ["docking", CollectionViewType.Docking], - ["tree", CollectionViewType.Tree], - ["stacking", CollectionViewType.Stacking], - ["masonry", CollectionViewType.Masonry], - ["pivot", CollectionViewType.Pivot], - ["linear", CollectionViewType.Linear] - ]); - - export const valueOf = (value: string) => { - return stringMapping.get(value.toLowerCase()); - }; - -} - -export interface CollectionRenderProps { - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; - active: () => boolean; - whenActiveChanged: (isActive: boolean) => void; -} - -export interface CollectionViewProps extends FieldViewProps { - onContextMenu?: (e: React.MouseEvent) => void; - children: (type: CollectionViewType, props: CollectionRenderProps) => JSX.Element | JSX.Element[] | null | (JSX.Element | null)[]; - className?: string; - contentRef?: React.Ref; -} - -@observer -export class CollectionBaseView extends React.Component { - @observable private static _safeMode = false; - static InSafeMode() { return this._safeMode; } - static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } - get collectionViewType(): CollectionViewType | undefined { - let Document = this.props.Document; - let viewField = Cast(Document.viewType, "number"); - if (CollectionBaseView._safeMode) { - if (viewField === CollectionViewType.Freeform) { - return CollectionViewType.Tree; - } - if (viewField === CollectionViewType.Invalid) { - return CollectionViewType.Freeform; - } - } - if (viewField !== undefined) { - return viewField; - } else { - return CollectionViewType.Invalid; - } - } - - active = (): boolean => { - var isSelected = this.props.isSelected(); - return isSelected || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; - } - - //TODO should this be observable? - private _isChildActive = false; - whenActiveChanged = (isActive: boolean) => { - this._isChildActive = isActive; - this.props.whenActiveChanged(isActive); - } - - - @action.bound - addDocument(doc: Doc): boolean { - let targetDataDoc = Doc.GetProto(this.props.Document); - let targetField = this.props.fieldKey; - Doc.AddDocToList(targetDataDoc, targetField, doc); - let extension = Doc.fieldExtensionDoc(targetDataDoc, targetField); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field - extension && (extension.lastModified = new DateField(new Date(Date.now()))); - Doc.GetProto(doc).lastOpened = new DateField; - return true; - } - - @action.bound - removeDocument(doc: Doc): boolean { - let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); - docView && SelectionManager.DeselectDoc(docView); - //TODO This won't create the field if it doesn't already exist - let targetDataDoc = this.props.Document; - let targetField = this.props.fieldKey; - let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); - index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - - if (index !== -1) { - value.splice(index, 1); - - // SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems(); - return true; - } - return false; - } - - // this is called with the document that was dragged and the collection to move it into. - // if the target collection is the same as this collection, then the move will be allowed. - // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. - @action.bound - moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { - return true; - } - return this.removeDocument(doc) ? addDocument(doc) : false; - } - - showIsTagged = () => { - const children = DocListCast(this.props.Document.data); - const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); - const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); - if (allTagged) { - return ( - - ); - } - return (null); - } - - render() { - const props: CollectionRenderProps = { - addDocument: this.addDocument, - removeDocument: this.removeDocument, - moveDocument: this.moveDocument, - active: this.active, - whenActiveChanged: this.whenActiveChanged, - }; - const viewtype = this.collectionViewType; - return ( -
- {this.showIsTagged()} - {viewtype !== undefined ? this.props.children(viewtype, props) : (null)} -
- ); - } - -} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 2cad41acb..47c355fc8 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -23,7 +23,7 @@ import { EditableView } from "../EditableView"; import { MainView } from '../MainView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { Templates } from '../Templates'; -import { CollectionViewType } from './CollectionBaseView'; +import { CollectionViewType } from './CollectionView'; import { CollectionSchemaPreview } from './CollectionSchemaView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss new file mode 100644 index 000000000..e4187e4d6 --- /dev/null +++ b/src/client/views/collections/CollectionView.scss @@ -0,0 +1,26 @@ +@import "../globalCssVariables"; + +.collectionView { + border-width: 0; + border-color: $light-color-secondary; + border-style: solid; + border-radius: 0 0 $border-radius $border-radius; + box-sizing: border-box; + border-radius: inherit; + width: 100%; + height: 100%; + overflow: auto; +} + +#google-tags { + transition: all 0.5s ease 0s; + width: 30px; + height: 30px; + position: absolute; + bottom: 15px; + left: 15px; + border: 2px solid black; + border-radius: 50%; + padding: 3px; + background: white; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index f8eb28ade..8d5694bf0 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -5,12 +5,9 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mo import { observer } from "mobx-react"; import * as React from 'react'; import { Id } from '../../../new_fields/FieldSymbols'; -import { StrCast, Cast } from '../../../new_fields/Types'; +import { StrCast, BoolCast, Cast } from '../../../new_fields/Types'; import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from '../ContextMenuItem'; -import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView'; import { CollectionDockingView } from "./CollectionDockingView"; import { AddCustomFreeFormLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; @@ -26,18 +23,75 @@ import { DocListCast } from '../../../new_fields/Doc'; import Lightbox from 'react-image-lightbox-with-rotate'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app export const COLLECTION_BORDER_WIDTH = 2; - +import { DateField } from '../../../new_fields/DateField'; +import { Doc, } from '../../../new_fields/Doc'; +import { listSpec } from '../../../new_fields/Schema'; +import { DocumentManager } from '../../util/DocumentManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import './CollectionView.scss'; +import { FieldViewProps, FieldView } from '../nodes/FieldView'; library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); +export enum CollectionViewType { + Invalid, + Freeform, + Schema, + Docking, + Tree, + Stacking, + Masonry, + Pivot, + Linear, +} + +export namespace CollectionViewType { + const stringMapping = new Map([ + ["invalid", CollectionViewType.Invalid], + ["freeform", CollectionViewType.Freeform], + ["schema", CollectionViewType.Schema], + ["docking", CollectionViewType.Docking], + ["tree", CollectionViewType.Tree], + ["stacking", CollectionViewType.Stacking], + ["masonry", CollectionViewType.Masonry], + ["pivot", CollectionViewType.Pivot], + ["linear", CollectionViewType.Linear] + ]); + + export const valueOf = (value: string) => stringMapping.get(value.toLowerCase()); +} + +export interface CollectionRenderProps { + addDocument: (document: Doc) => boolean; + removeDocument: (document: Doc) => boolean; + moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + active: () => boolean; + whenActiveChanged: (isActive: boolean) => void; +} + @observer export class CollectionView extends React.Component { - public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } private _reactionDisposer: IReactionDisposer | undefined; + private _isChildActive = false; //TODO should this be observable? @observable private _isLightboxOpen = false; @observable private _curLightboxImg = 0; @observable private _collapsed = true; + @observable private static _safeMode = false; + public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } + + get collectionViewType(): CollectionViewType | undefined { + let viewField = Cast(this.props.Document.viewType, "number"); + if (CollectionView._safeMode) { + if (viewField === CollectionViewType.Freeform) { + return CollectionViewType.Tree; + } + if (viewField === CollectionViewType.Invalid) { + return CollectionViewType.Freeform; + } + } + return viewField === undefined ? CollectionViewType.Invalid : viewField; + } componentDidMount = () => { this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus), @@ -51,32 +105,73 @@ export class CollectionView extends React.Component { }); } - componentWillUnmount = () => { - this._reactionDisposer && this._reactionDisposer(); + componentWillUnmount = () => this._reactionDisposer && this._reactionDisposer(); + + // bcz: Argh? What's the height of the collection chromes?? + chromeHeight = () => (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0); + + active = () => this.props.isSelected() || BoolCast(this.props.Document.forceActive) || this._isChildActive || this.props.renderDepth === 0; + + whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; + + @action.bound + addDocument(doc: Doc): boolean { + let targetDataDoc = Doc.GetProto(this.props.Document); + Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); + let extension = Doc.fieldExtensionDoc(targetDataDoc, this.props.fieldKey); // set metadata about the field being rendered (ie, the set of documents) on an extension field for that field + extension && (extension.lastModified = new DateField(new Date(Date.now()))); + Doc.GetProto(doc).lastOpened = new DateField; + return true; + } + + @action.bound + removeDocument(doc: Doc): boolean { + let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); + docView && SelectionManager.DeselectDoc(docView); + let value = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + + ContextMenu.Instance.clearItems(); + if (index !== -1) { + value.splice(index, 1); + return true; + } + return false; + } + + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. + @action.bound + moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { + return true; + } + return this.removeDocument(doc) ? addDocument(doc) : false; } - // bcz: Argh? What's the height of the collection chomes?? - chromeHeight = () => { - return (this.props.ChromeHeight ? this.props.ChromeHeight() : 0) + (this.props.Document.chromeStatus === "enabled" ? -60 : 0); + showIsTagged = () => { + const children = DocListCast(this.props.Document[this.props.fieldKey]); + const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); + const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); + return !allTagged ? (null) : ; } private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; + let props = { ...this.props, ...renderProps, chromeCollapsed: this._collapsed, ChromeHeight: this.chromeHeight, CollectionView: this, annotationsKey: "" }; switch (type) { - case CollectionViewType.Schema: return (); - // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - case CollectionViewType.Docking: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } - case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } - case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } - case CollectionViewType.Linear: { return (); } + case CollectionViewType.Schema: return (); + case CollectionViewType.Docking: return (); + case CollectionViewType.Tree: return (); + case CollectionViewType.Linear: { return (); } + case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (); } + case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (); } + case CollectionViewType.Pivot: { this.props.Document.freeformLayoutEngine = "pivot"; return (); } case CollectionViewType.Freeform: - default: - this.props.Document.freeformLayoutEngine = undefined; - return (); + default: { this.props.Document.freeformLayoutEngine = undefined; return (); } } - return (null); } @action @@ -87,22 +182,18 @@ export class CollectionView extends React.Component { private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => { // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip - if (this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking) { - return [(null), this.SubViewHelper(type, renderProps)]; - } - return [ - , - this.SubViewHelper(type, renderProps) - ]; + let chrome = this.props.Document.chromeStatus === "disabled" || type === CollectionViewType.Docking ? (null) : + ; + return [chrome, this.SubViewHelper(type, renderProps)]; } onContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let existingVm = ContextMenu.Instance.findByDescription("View Modes..."); - let subItems: ContextMenuProps[] = existingVm && "subitems" in existingVm ? existingVm.subitems : []; + let subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; subItems.push({ description: "Freeform", event: () => { this.props.Document.viewType = CollectionViewType.Freeform; }, icon: "signature" }); - if (CollectionBaseView.InSafeMode()) { + if (CollectionView._safeMode) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); @@ -126,7 +217,7 @@ export class CollectionView extends React.Component { !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); let existing = ContextMenu.Instance.findByDescription("Layout..."); - let layoutItems: ContextMenuProps[] = existing && "subitems" in existing ? existing.subitems : []; + let layoutItems = existing && "subitems" in existing ? existing.subitems : []; layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }); !existing && ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "hand-point-right" }); ContextMenu.Instance.addItem({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); @@ -142,15 +233,23 @@ export class CollectionView extends React.Component { onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); } - render() { - return (<> - - {this.SubView} - - + const props: CollectionRenderProps = { + addDocument: this.addDocument, + removeDocument: this.removeDocument, + moveDocument: this.moveDocument, + active: this.active, + whenActiveChanged: this.whenActiveChanged, + }; + return (
+ {this.showIsTagged()} + {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} {this.lightbox(DocListCast(this.props.Document[this.props.fieldKey]).filter(d => d.type === DocumentType.IMG).map(d => Cast(d.data, ImageField) ? Cast(d.data, ImageField)!.url.href : ""))} - - ); +
); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index dd5e630e4..cfc6c2a3f 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -14,7 +14,7 @@ import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss"; import { DocLike } from "../MetadataEntryMenu"; -import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionViewType } from "./CollectionView"; import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import * as Autosuggest from 'react-autosuggest'; diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 7f2913214..8b6fa330c 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -7,7 +7,7 @@ import { Id } from "../../../new_fields/FieldSymbols"; import { SearchUtil } from "../../util/SearchUtil"; import { CollectionDockingView } from "./CollectionDockingView"; import { NumCast } from "../../../new_fields/Types"; -import { CollectionViewType } from "./CollectionBaseView"; +import { CollectionViewType } from "./CollectionView"; import { DocumentButtonBar } from "../DocumentButtonBar"; import { DocumentManager } from "../../util/DocumentManager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0419bc3fa..d7350fe1a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -35,7 +35,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { documentSchema, positionSchema } from "../../DocComponent"; +import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index e0bf4a2f0..4f86eeb2b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -15,7 +15,7 @@ import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { CollectionViewType } from "../CollectionBaseView"; +import { CollectionViewType } from "../CollectionView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index ef194624a..efe2c7f2a 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -5,7 +5,7 @@ import { FieldViewProps, FieldView } from "../nodes/FieldView"; import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc"; import { undoBatch } from "../../util/UndoManager"; import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { SelectionManager } from "../../util/SelectionManager"; import { DocumentManager } from "../../util/DocumentManager"; diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 1531d825b..beb2b30fd 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -66,7 +66,8 @@ export class ButtonBox extends DocComponent(Butt @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData && e.target) { - this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments); + this.props.Document[(e.target as any).textContent] = new List(de.data.droppedDocuments.map((d, i) => + d.onDragStart ? de.data.draggedDocuments[i] : d)); e.stopPropagation(); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f741dae9a..05080b824 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -26,7 +26,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import SharingManager from '../../util/SharingManager'; import { Transform } from "../../util/Transform"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionViewType } from '../collections/CollectionBaseView'; +import { CollectionViewType } from '../collections/CollectionView'; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 7ec5d0471..cbb83b511 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -10,7 +10,7 @@ import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; import { DocumentManager } from "../../util/DocumentManager"; import { undoBatch } from "../../util/UndoManager"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView } from "../collections/CollectionView"; import { ContextMenu } from "../ContextMenu"; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 5f758f496..7a1b4f9fb 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -12,7 +12,7 @@ import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnFalse } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { Transform } from "../../util/Transform"; -import { CollectionViewType } from '../collections/CollectionBaseView'; +import { CollectionViewType } from '../collections/CollectionView'; import { CollectionSchemaPreview } from '../collections/CollectionSchemaView'; import { DocComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; @@ -51,7 +51,7 @@ export class PresElementBox extends DocComponent(P @computed get indexInPres() { return DocListCast(this.presentationDoc[this.Document.presBoxKey || ""]).indexOf(this.props.Document); } @computed get presentationDoc() { return Cast(this.Document.presBox, Doc) as Doc; } - @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc } + @computed get targetDoc() { return this.Document.presentationTargetDoc as Doc; } @computed get currentIndex() { return NumCast(this.presentationDoc.selectedDoc); } /** diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 60307065a..f1d825aa0 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -15,7 +15,7 @@ import { LinkManager } from "../../util/LinkManager"; import { SearchUtil } from "../../util/SearchUtil"; import { Transform } from "../../util/Transform"; import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; -import { CollectionViewType } from "../collections/CollectionBaseView"; +import { CollectionViewType } from "../collections/CollectionView"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ContextMenu } from "../ContextMenu"; import { DocumentView } from "../nodes/DocumentView"; -- cgit v1.2.3-70-g09d2 From 43b09cb2aded66e67006d99e544d90637a62c048 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 21:54:10 -0400 Subject: improved styling and colors of color picker on richtexteditor --- src/client/util/TooltipTextMenu.scss | 18 +++++++++ src/client/util/TooltipTextMenu.tsx | 75 +++++++++++++++++++++++++++--------- src/new_fields/SchemaHeaderField.ts | 11 ++++++ 3 files changed, 85 insertions(+), 19 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index f94799936..794998a01 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -182,6 +182,24 @@ } } +#colorPicker { + position: relative; + + svg { + width: 18px; + height: 18px; + } + + .buttonColor { + position: absolute; + top: 24px; + left: 1px; + width: 24px; + height: 4px; + margin-top: 0; + } +} + .strong, .heading { font-weight: bold; } .em { font-style: italic; } .underline {text-decoration: underline} diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 32a910497..92765b1e0 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -21,7 +21,7 @@ import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; -import { PastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; +import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -58,6 +58,8 @@ export class TooltipTextMenu { private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; + private colorDom?: Node; + private colorDropdownDom?: Node; // private _collapseBtn?: MenuItem; @@ -99,7 +101,7 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, - { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } + // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; // add menu items @@ -135,8 +137,10 @@ export class TooltipTextMenu { }); // color menu - this.tooltip.appendChild(this.createColorTool().render(this.view).dom); - this.tooltip.appendChild(this.createColorDropdown().render(this.view).dom); + this.colorDom = this.createColorTool().render(this.view).dom; + this.colorDropdownDom = this.createColorDropdown().render(this.view).dom; + this.tooltip.appendChild(this.colorDom); + this.tooltip.appendChild(this.colorDropdownDom); // link menu this.updateLinkMenu(); @@ -760,14 +764,35 @@ export class TooltipTextMenu { return true; } + createHighlightTool() { + + } + createColorTool() { return new MenuItem({ - title: "Summarize", - label: "Summarize", - icon: icons.join, + title: "Color", + // label: "Color", + // icon: icon, css: "color:white;", class: "menuicon", execEvent: "", + render() { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"); + svg.appendChild(path); + + let color = document.createElement("div"); + color.className = "buttonColor"; + color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString(); + + let wrapper = document.createElement("div"); + wrapper.id = "colorPicker"; + wrapper.appendChild(svg); + wrapper.appendChild(color); + return wrapper; + }, run: (state, dispatch) => { TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } @@ -798,15 +823,15 @@ export class TooltipTextMenu { colorsWrapper.className = "colorPicker-wrapper"; let colors = [ - PastelSchemaPalette.get("pink2"), - PastelSchemaPalette.get("purple4"), - PastelSchemaPalette.get("bluegreen1"), - PastelSchemaPalette.get("yellow4"), - PastelSchemaPalette.get("red2"), - PastelSchemaPalette.get("bluegreen7"), - PastelSchemaPalette.get("bluegreen5"), - PastelSchemaPalette.get("orange1"), - "#f1efeb", + DarkPastelSchemaPalette.get("pink2"), + DarkPastelSchemaPalette.get("purple4"), + DarkPastelSchemaPalette.get("bluegreen1"), + DarkPastelSchemaPalette.get("yellow4"), + DarkPastelSchemaPalette.get("red2"), + DarkPastelSchemaPalette.get("bluegreen7"), + DarkPastelSchemaPalette.get("bluegreen5"), + DarkPastelSchemaPalette.get("orange1"), + "#757472", "#000" ]; @@ -815,7 +840,19 @@ export class TooltipTextMenu { button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; if (color) { button.style.backgroundColor = color; - button.onclick = e => TooltipTextMenuManager.Instance.color = color; + button.onclick = e => { + TooltipTextMenuManager.Instance.color = color; + + TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); + + // update color menu + let colorDom = self.createColorTool().render(self.view).dom; + let colorDropdownDom = self.createColorDropdown().render(self.view).dom; + self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom); + self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom); + self.colorDom = colorDom; + self.colorDropdownDom = colorDropdownDom; + }; } colorsWrapper.appendChild(button); }); @@ -1183,8 +1220,8 @@ export class TooltipTextMenu { //UPDATE LIST ITEM DROPDOWN // update link dropdown - let dropdown = await this.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(this.view).dom; + let linkDropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = linkDropdown.render(this.view).dom; this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); this._linkDropdownDom = newLinkDropdowndom; diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 92d0aec9a..42a8485ac 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -41,6 +41,17 @@ export const PastelSchemaPalette = new Map([ export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; +export const DarkPastelSchemaPalette = new Map([ + ["pink2", "#c932b0"], + ["purple4", "#913ad6"], + ["bluegreen1", "#3978ed"], + ["bluegreen7", "#2adb3e"], + ["bluegreen5", "#21b0eb"], + ["yellow4", "#edcc0c"], + ["red2", "#eb3636"], + ["orange1", "#f2740f"], +]); + @scriptingGlobal @Deserializable("schemaheader") export class SchemaHeaderField extends ObjectField { -- cgit v1.2.3-70-g09d2 From bd817ac118c53845c882b89c4b8a110dc8fe10c2 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 22:22:34 -0400 Subject: added dropdown to highlighter tool to choose colors --- src/client/util/RichTextSchema.tsx | 17 +++++- src/client/util/TooltipTextMenu.tsx | 113 ++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index e943b71e8..22756be24 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -321,8 +321,21 @@ export const marks: { [index: string]: MarkSpec } = { }], toDOM(node: any) { return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; - // ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - // ["a", { ...node.attrs }, 0]; + } + }, + + highlight2: { + attrs: { + highlight: { default: "transparent" } + }, + inclusive: false, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { highlight: dom.getAttribute("backgroundColor") }; + } + }], + toDOM(node: any) { + return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 92765b1e0..0626eb504 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,4 +1,4 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; +import { library, dom } from '@fortawesome/fontawesome-svg-core'; import { faListUl } from '@fortawesome/free-solid-svg-icons'; import { action, observable } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css @@ -60,6 +60,8 @@ export class TooltipTextMenu { private listTypeBtnDom?: Node; private colorDom?: Node; private colorDropdownDom?: Node; + private highlightDom?: Node; + private highlightDropdownDom?: Node; // private _collapseBtn?: MenuItem; @@ -136,6 +138,12 @@ export class TooltipTextMenu { }); + // highlight menu + this.highlightDom = this.createHighlightTool().render(this.view).dom; + this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom; + this.tooltip.appendChild(this.highlightDom); + this.tooltip.appendChild(this.highlightDropdownDom); + // color menu this.colorDom = this.createColorTool().render(this.view).dom; this.colorDropdownDom = this.createColorDropdown().render(this.view).dom; @@ -765,14 +773,111 @@ export class TooltipTextMenu { } createHighlightTool() { + return new MenuItem({ + title: "Highlight", + css: "color:white;", + class: "menuicon", + execEvent: "", + render() { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z"); + svg.appendChild(path); + + let color = document.createElement("div"); + color.className = "buttonColor"; + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString() === "transparent" ? "#fff" : TooltipTextMenuManager.Instance.highlight.toString(); + + let wrapper = document.createElement("div"); + wrapper.id = "colorPicker"; + wrapper.appendChild(svg); + wrapper.appendChild(color); + return wrapper; + }, + run: (state, dispatch) => { + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch); + } + }); + } + + public static insertHighlight(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + + let highlightMark = state.schema.mark(state.schema.marks.highlight2, { highlight: color }); + dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); + } + + createHighlightDropdown() { + // menu item for color picker + let self = this; + let colors = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Change highlight:"; + + let colorsWrapper = document.createElement("div"); + colorsWrapper.className = "colorPicker-wrapper"; + + let colors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + // "#f1f0eb", + "transparent" + ]; + colors.forEach(color => { + let button = document.createElement("button"); + button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; + if (color) { + button.style.backgroundColor = color === "transparent" ? "#fff" : color; + button.style.color = "black"; + button.textContent = color === "transparent" ? "X" : ""; + button.onclick = e => { + TooltipTextMenuManager.Instance.highlight = color; + + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch); + + // update color menu + let highlightDom = self.createHighlightTool().render(self.view).dom; + let highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom; + self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom); + self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom); + self.highlightDom = highlightDom; + self.highlightDropdownDom = highlightDropdownDom; + }; + } + colorsWrapper.appendChild(button); + }); + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(colorsWrapper); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; + return colorDropdown; } createColorTool() { return new MenuItem({ title: "Color", - // label: "Color", - // icon: icon, css: "color:white;", class: "menuicon", execEvent: "", @@ -796,7 +901,6 @@ export class TooltipTextMenu { run: (state, dispatch) => { TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } - }); } @@ -1406,6 +1510,7 @@ class TooltipTextMenuManager { public _brushIsEmpty: boolean = true; public color: String = "#000"; + public highlight: String = "transparent"; public activeMenu: TooltipTextMenu | undefined; -- cgit v1.2.3-70-g09d2 From 66a15a7ac6a6907b398f142554f5d1a66988b630 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 22:57:02 -0400 Subject: switched out text formatting buttons on rich text editor for fontawesome svg --- src/client/util/TooltipTextMenu.scss | 1 + src/client/util/TooltipTextMenu.tsx | 36 +++++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 11 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 794998a01..16585d20b 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -188,6 +188,7 @@ svg { width: 18px; height: 18px; + margin-top: 11px; } .buttonColor { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 0626eb504..d32889dd0 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -95,14 +95,14 @@ export class TooltipTextMenu { this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; - // init buttons to the tooltip + // init buttons to the tooltip -- paths to svgs are obtained from fontawesome let items = [ - { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, - { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, - { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") }, - { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, - { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, - { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, + { command: toggleMark(schema.marks.strong), dom: this.svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") }, + { command: toggleMark(schema.marks.em), dom: this.svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") }, + { command: toggleMark(schema.marks.underline), dom: this.svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") }, + { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") }, + { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, + { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; @@ -787,7 +787,7 @@ export class TooltipTextMenu { let color = document.createElement("div"); color.className = "buttonColor"; - color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString() === "transparent" ? "#fff" : TooltipTextMenuManager.Instance.highlight.toString(); + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString(); let wrapper = document.createElement("div"); wrapper.id = "colorPicker"; @@ -832,7 +832,7 @@ export class TooltipTextMenu { PastelSchemaPalette.get("bluegreen7"), PastelSchemaPalette.get("bluegreen5"), PastelSchemaPalette.get("orange1"), - // "#f1f0eb", + "white", "transparent" ]; @@ -840,8 +840,7 @@ export class TooltipTextMenu { let button = document.createElement("button"); button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; if (color) { - button.style.backgroundColor = color === "transparent" ? "#fff" : color; - button.style.color = "black"; + button.style.backgroundColor = color; button.textContent = color === "transparent" ? "X" : ""; button.onclick = e => { TooltipTextMenuManager.Instance.highlight = color; @@ -1239,6 +1238,21 @@ export class TooltipTextMenu { return span; } + svgIcon(name: string, title: string = name, dpath: string) { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", dpath); + svg.appendChild(path); + + let span = document.createElement("span"); + span.className = name + " menuicon"; + span.title = title; + span.appendChild(svg); + + return span; + } + //method for checking whether node can be inserted canInsert(state: EditorState, nodeType: NodeType>) { let $from = state.selection.$from; -- cgit v1.2.3-70-g09d2 From 910e4d8a22a312d2ca0b8a970ff434604f7c6f91 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 23 Oct 2019 23:35:01 -0400 Subject: several fixes to audio documents to make linking work better and to customize the audio controls UI --- src/client/documents/Documents.ts | 3 +- src/client/util/DocumentManager.ts | 4 +- src/client/views/nodes/AudioBox.scss | 62 +++++++++++++++++++--- src/client/views/nodes/AudioBox.tsx | 94 +++++++++++++++++++++------------ src/client/views/nodes/DocuLinkBox.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 16 ++++-- src/new_fields/Doc.ts | 2 + 7 files changed, 134 insertions(+), 51 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cad417d33..f26594e04 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -171,7 +171,7 @@ export namespace Docs { }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: data }, - options: { height: 32 } + options: { height: 35, backgroundColor: "lightGray", borderRounding: "20%" } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: data }, @@ -691,6 +691,7 @@ export namespace DocUtils { } }); } + export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) { let sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0f2a47dd0..346e88f40 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -101,10 +101,10 @@ export class DocumentManager { @computed public get LinkedDocumentViews() { let pairs = DocumentManager.Instance.DocumentViews.filter(dv => - dv.isSelected() || Doc.IsBrushed(dv.props.Document) // draw links from DocumentViews that are selected or brushed OR + (dv.isSelected() || Doc.IsBrushed(dv.props.Document)) // draw links from DocumentViews that are selected or brushed OR || DocumentManager.Instance.DocumentViews.some(dv2 => { // Documentviews which let rest = DocListCast(dv2.props.Document.links).some(l => Doc.AreProtosEqual(l, dv.props.Document));// are link doc anchors - let init = dv2.isSelected() || Doc.IsBrushed(dv2.props.Document); // on a view that is selected or brushed + let init = (dv2.isSelected() || Doc.IsBrushed(dv2.props.Document)) && dv2.Document.type !== DocumentType.AUDIO; // on a view that is selected or brushed return init && rest; }) ).reduce((pairs, dv) => { diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 14b90c570..3d6f6c4f9 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,10 +1,10 @@ .audiobox-container, .audiobox-container-interactive { width: 100%; height: 100%; - min-height: 32px; position: inherit; display:flex; pointer-events: all; + cursor:default; .audiobox-handle { width:20px; height:100%; @@ -35,18 +35,66 @@ width:100%; height:100%; position: relative; - display: grid; + display: flex; .audiobox-player { margin-top:auto; margin-bottom:auto; width:100%; + height: 80%; position: relative; - display: grid; - .audiobox-marker { - position:absolute; - width:10px; + display: flex; + .audiobox-playhead { + position: relative; + margin-top: auto; + margin-bottom: auto; + width: 25px; + } + .audiobox-timeline { + position:relative; height:100%; - background:green; + width:100%; + .audiobox-current { + width: 1px; + height:100%; + background-color: red; + position: absolute;; + } + .audiobox-linker { + position:absolute; + width:15px; + min-height:10px; + height:15px; + margin-left:-2.55px; + background:gray; + border-radius: 100%; + background-color: transparent; + box-shadow: black 2px 2px 1px; + .docuLinkBox-cont { + width:15px !important; + height:15px !important; + left: calc(100% - 15px) !important; + top: calc(100% - 15px) !important; + } + } + .audiobox-linker:hover { + transform:scale(1.5); + } + .audiobox-marker-container { + position:absolute; + width:10px; + height:100%; + background:gray; + border-radius: 5px; + box-shadow: black 2px 2px 1px; + .audiobox-marker { + position:relative; + height: calc(100% - 15px); + margin-top: 15px; + } + .audio-marker:hover { + border: orange 2px solid; + } + } } } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 57360272e..3933c6257 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -7,17 +7,18 @@ import { AudioField, nullAudio } from "../../../new_fields/URLField"; import { DocExtendableComponent } from "../DocComponent"; import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { documentSchema } from "../../../new_fields/documentSchemas"; -import { Utils } from "../../../Utils"; +import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent } from "../../../Utils"; import { RouteStore } from "../../../server/RouteStore"; import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx"; import { DateField } from "../../../new_fields/DateField"; import { SelectionManager } from "../../util/SelectionManager"; -import { Doc, DocListCast } from "../../../new_fields/Doc"; +import { Doc, DocListCast, WidthSym } from "../../../new_fields/Doc"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; import { Id } from "../../../new_fields/FieldSymbols"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { DocumentManager } from "../../util/DocumentManager"; +import { DocumentView } from "./DocumentView"; interface Window { MediaRecorder: MediaRecorder; @@ -44,16 +45,14 @@ export class AudioBox extends DocExtendableComponent AudioBox._scrubTime = timeInMillisFrom1970); public static ActiveRecordings: Doc[] = []; componentDidMount() { - runInAction(() => this._duration = NumCast(this.dataDoc.duration, 1)); runInAction(() => this._audioState = this.path ? "recorded" : "unrecorded"); this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { @@ -75,14 +74,12 @@ export class AudioBox extends DocExtendableComponent { + timecodeChanged = () => { const extensionDoc = this.extensionDoc; const htmlEle = this._ele; const start = extensionDoc && DateCast(extensionDoc.recordingStart); - if (htmlEle && !htmlEle.paused && start) { - htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = this._duration = htmlEle.duration); - setTimeout(this.updateHighlights, 30); - this.Document.currentTimecode = htmlEle.currentTime; + if (start && htmlEle) { + htmlEle && htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration); DocListCast(this.dataDoc.links).map(l => { let la1 = l.anchor1 as Doc; let linkTime = NumCast(l.anchor2Timecode); @@ -90,25 +87,29 @@ export class AudioBox extends DocExtendableComponent this._lastUpdate && linkTime < htmlEle.currentTime) { + if (linkTime > NumCast(this.Document.currentTimecode) && linkTime < htmlEle.currentTime) { Doc.linkFollowHighlight(la1); } }); - this._lastUpdate = htmlEle.currentTime; + this.Document.currentTimecode = htmlEle.currentTime; } } + pause = action(() => { + this._ele!.pause(); + this._playing = false; + }) + playFrom = (seekTimeInSeconds: number) => { if (this._ele) { if (seekTimeInSeconds < 0) { - this._ele.pause(); + this.pause(); } else if (seekTimeInSeconds <= this._ele.duration) { this._ele.currentTime = seekTimeInSeconds; this._ele.play(); - this._lastUpdate = seekTimeInSeconds; - setTimeout(this.updateHighlights, 0); + runInAction(() => this._playing = true); } else { - this._ele.pause(); + this.pause(); } } } @@ -170,6 +171,7 @@ export class AudioBox extends DocExtendableComponent { this._recorder.stop(); + this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000; this._audioState = "recorded"; let ind = AudioBox.ActiveRecordings.indexOf(this.props.Document); ind !== -1 && (AudioBox.ActiveRecordings.splice(ind, 1)); @@ -182,12 +184,15 @@ export class AudioBox extends DocExtendableComponent setTimeout(this.updateHighlights, 30); onPlay = (e: any) => this.playFrom(this._ele!.paused ? this._ele!.currentTime : -1); + onStop = (e: any) => { + this.pause(); + this._ele!.currentTime = 0; + e.stopPropagation(); + } setRef = (e: HTMLAudioElement | null) => { - e && e.addEventListener("play", this.playClick as any); - e && e.addEventListener("timeupdate", () => this.props.Document.currentTimecode = e!.currentTime); + e && e.addEventListener("timeupdate", action(() => this.Document.currentTimecode = e!.currentTime)); this._ele = e; } @@ -216,21 +221,40 @@ export class AudioBox extends DocExtendableComponent :
- - {DocListCast(this.dataDoc.links).map((l, i) => { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - let linkTime = NumCast(l.anchor2Timecode); - if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; - linkTime = NumCast(l.anchor1Timecode); - } - return
- DocumentManager.Instance.FollowLink(l, la2, document => this.props.addDocTab(document, undefined, "onRight"), false)} - style={{ left: `${linkTime / this._duration * 100}%` }} />; - })} - {this.audio} + +
+
e.stopPropagation()} + onPointerDown={e => { + if (e.button === 0 && !e.ctrlKey) { + let rect = (e.target as any).getBoundingClientRect(); + this._ele!.currentTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration); + this.pause(); + e.stopPropagation(); + } + }} > + {DocListCast(this.dataDoc.links).map((l, i) => { + let la1 = l.anchor1 as Doc; + let la2 = l.anchor2 as Doc; + let linkTime = NumCast(l.anchor2Timecode); + if (Doc.AreProtosEqual(la1, this.dataDoc)) { + la1 = l.anchor2 as Doc; + la2 = l.anchor1 as Doc; + linkTime = NumCast(l.anchor1Timecode); + } + return !linkTime ? (null) :
+
+ +
+
Doc.linkFollowHighlight(la1)} + onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }} + onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} /> +
; + })} +
+ {this.audio} +
} diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index 7119b0db0..f2ab6fcd8 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -33,7 +33,7 @@ export class DocuLinkBox extends DocComponent(Doc document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); + (e.button === 0 && !e.ctrlKey) && e.stopPropagation(); } onPointerMove = action((e: PointerEvent) => { let cdiv = this._ref.current!.parentElement; @@ -43,7 +43,7 @@ export class DocuLinkBox extends DocComponent(Doc let separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); let dragdist = Math.sqrt((pt[0] - this._downx) * (pt[0] - this._downx) + (pt[1] - this._downy) * (pt[1] - this._downy)); if (separation > 100) { - DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], this.props.ContainingCollectionDoc as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. + DragLinksAsDocuments(this._ref.current!, pt[0], pt[1], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } else if (dragdist > separation) { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c82fd0f0f..ed93aa83e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -41,6 +41,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); +import { link } from 'fs'; library.add(fa.faEdit); library.add(fa.faTrash); @@ -585,8 +586,16 @@ export class DocumentView extends DocComponent(Docu layoutKey={this.props.layoutKey || "layout"} DataDoc={this.props.DataDoc} />); } - linkEndpoint = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; - linkEndpointDoc = (linkDoc: Doc) => Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? Cast(linkDoc.anchor1, Doc) as Doc : Cast(linkDoc.anchor2, Doc) as Doc; + linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document); + + // used to decide whether a link document should be created or not. + // if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here. + // would be good to generalize this some way. + isNonTemporalLink = (linkDoc: Doc) => { + let anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc; + let ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1Timecode : linkDoc.anchor2Timecode; + return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true; + } render() { if (!this.props.Document) return (null); @@ -658,8 +667,7 @@ export class DocumentView extends DocComponent(Docu onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)} > - {this.Document.links && DocListCast(this.Document.links).map((d, i) => - //this.linkEndpointDoc(d).type === DocumentType.PDFANNO ? (null) : + {this.Document.links && DocListCast(this.Document.links).filter(this.isNonTemporalLink).map((d, i) =>
)} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 08cb66d5f..6aad4a6be 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -669,6 +669,8 @@ export namespace Doc { return doc; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } + export function linkFollowUnhighlight() { Doc.UnhighlightAll(); document.removeEventListener("pointerdown", linkFollowUnhighlight); -- cgit v1.2.3-70-g09d2 From 45074de50007e0693df8835643464da962e62620 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 24 Oct 2019 10:43:49 -0400 Subject: fixed width of rich text editor --- src/client/util/TooltipTextMenu.scss | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 16585d20b..1f619b4f5 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -101,8 +101,9 @@ transform: translateY(-85px); pointer-events: all; height: 35px; - width: 650px; + // width: 650px; padding: 3px; + padding-bottom: 5px; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); display: flex; align-items: center; @@ -122,10 +123,6 @@ } } -#link-drag { - background-color: #323232; -} - .menuicon { width: 25px; height: 25px; @@ -188,7 +185,7 @@ svg { width: 18px; height: 18px; - margin-top: 11px; + // margin-top: 11px; } .buttonColor { @@ -201,12 +198,14 @@ } } - .strong, .heading { font-weight: bold; } - .em { font-style: italic; } - .underline {text-decoration: underline} - .superscript {vertical-align:super} - .subscript { vertical-align:sub } - .strikethrough {text-decoration-line:line-through} +#link-drag { + background-color: #323232; +} + +.underline svg { + margin-top: 15px; +} + .font-size-indicator { font-size: 12px; padding-right: 0px; @@ -299,8 +298,9 @@ left: -27px; top: 31px; background-color: #323232; + border: 1px solid #4d4d4d; color: $light-color-secondary; - border: none; + // border: none; // border: 1px solid $light-color-secondary; border-radius: 0 6px 6px 6px; padding: 3px; -- cgit v1.2.3-70-g09d2 From b7353705ee06292e570c9847d72287190f3f42ed Mon Sep 17 00:00:00 2001 From: Stanley Yip Date: Tue, 29 Oct 2019 19:09:04 -0400 Subject: started implementing ink select with document decorations --- src/client/util/SelectionManager.ts | 30 +++++++++++ src/client/views/DocumentDecorations.tsx | 57 ++++++++++++++------- src/client/views/InkSelectDecorations.scss | 5 ++ src/client/views/InkSelectDecorations.tsx | 59 ++++++++++++++++++++++ src/client/views/MainView.tsx | 2 + src/client/views/Touchable.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- .../collections/collectionFreeForm/MarqueeView.tsx | 18 +++++-- 8 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 src/client/views/InkSelectDecorations.scss create mode 100644 src/client/views/InkSelectDecorations.tsx (limited to 'src/client/util') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 2d717ca57..3ae43e029 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -11,6 +11,7 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; + @observable SelectedInk: Array> = []; @action @@ -41,6 +42,20 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; + manager.SelectedInk = []; + } + + @action + SelectInk(ink: Map, ctrlPressed: boolean): void { + if (manager.SelectedInk.indexOf(ink) === -1) { + if (!ctrlPressed) { + this.DeselectAll(); + } + + manager.SelectedInk.push(ink); + } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { + manager.SelectedInk = [ink]; + } } } @@ -53,6 +68,10 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } + export function SelectInk(ink: Map, ctrlPressed: boolean): void { + manager.SelectInk(ink, ctrlPressed); + } + export function IsSelected(doc: DocumentView): boolean { return manager.SelectedDocuments.indexOf(doc) !== -1; } @@ -75,4 +94,15 @@ export namespace SelectionManager { export function SelectedDocuments(): Array { return manager.SelectedDocuments.slice(); } + + export function SelectedInk(): Array> { + return manager.SelectedInk.slice(); + } + + export function AllSelected(): Array> { + let arr: Array> = []; + arr = SelectionManager.SelectedDocuments(); + arr.push(...SelectionManager.SelectedInk()); + return arr; + } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3d73f048d..90d6e1e8d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -25,6 +25,8 @@ import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); import { TooltipTextMenu } from '../util/TooltipTextMenu'; +import { InkingCanvas } from './InkingCanvas'; +import { StrokeData } from '../../new_fields/InkField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -166,23 +168,42 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get Bounds(): { x: number, y: number, b: number, r: number } { let x = this._forceUpdate; - this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { - if (documentView.props.renderDepth === 0 || - Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) { - return bounds; + this._lastBox = SelectionManager.AllSelected().reduce((bounds, docViewOrInk) => { + if (docViewOrInk instanceof DocumentView) { + if (docViewOrInk.props.renderDepth === 0 || + Doc.AreProtosEqual(docViewOrInk.props.Document, CurrentUserUtils.UserDocument)) { + return bounds; + } + let transform = (docViewOrInk.props.ScreenToLocalTransform().scale(docViewOrInk.props.ContentScaling())).inverse(); + if (transform.TranslateX === 0 && transform.TranslateY === 0) { + setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds + return this._lastBox; + } + + var [sptX, sptY] = transform.transformPoint(0, 0); + let [bptX, bptY] = transform.transformPoint(docViewOrInk.props.PanelWidth(), docViewOrInk.props.PanelHeight()); + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; } - let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse(); - if (transform.TranslateX === 0 && transform.TranslateY === 0) { - setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds - return this._lastBox; + else { + let left = bounds.x; + let top = bounds.y; + let right = bounds.r; + let bottom = bounds.b; + docViewOrInk.forEach((value: StrokeData, key: string) => { + value.pathData.map(val => { + left = Math.min(val.x, left); + top = Math.min(val.y, top); + right = Math.max(val.x, right); + bottom = Math.max(val.y, bottom); + }); + }); + return { + x: left, y: top, r: right, b: bottom + }; } - - var [sptX, sptY] = transform.transformPoint(0, 0); - let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight()); - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); return this._lastBox; } @@ -559,7 +580,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } render() { var bounds = this.Bounds; - let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; + let seldoc = SelectionManager.AllSelected().length ? SelectionManager.AllSelected()[0] : undefined; if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return (null); } @@ -586,7 +607,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, pointerEvents: this.Interacting ? "none" : "all", - zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0, + zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
e.preventDefault()}>
e.preventDefault()}>
- + {(SelectionManager.SelectedDocuments.length && SelectionManager.SelectedDocuments()[0]) ? : (null)}
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss new file mode 100644 index 000000000..daff58fd6 --- /dev/null +++ b/src/client/views/InkSelectDecorations.scss @@ -0,0 +1,5 @@ +.inkSelectDecorations { + position: absolute; + border: black 1px solid; + z-index: 9001; +} \ No newline at end of file diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx new file mode 100644 index 000000000..a8eef3305 --- /dev/null +++ b/src/client/views/InkSelectDecorations.tsx @@ -0,0 +1,59 @@ +import React = require("react"); +import { Touchable } from "./Touchable"; +import { StrokeData } from "../../new_fields/InkField"; +import { observer } from "mobx-react"; +import { computed, observable, action, runInAction } from "mobx"; +import "./InkSelectDecorations.scss" + +@observer +export default class InkSelectDecorations extends Touchable { + static Instance: InkSelectDecorations; + + @observable private _selectedInkNodes: Map = new Map(); + + constructor(props: Readonly<{}>) { + super(props); + + InkSelectDecorations.Instance = this; + } + + @action + public SetSelected = (inkNodes: Map, keepOld: boolean = false) => { + if (!keepOld) { + this._selectedInkNodes = new Map(); + } + inkNodes.forEach((value: any, key: any) => { + runInAction(() => this._selectedInkNodes.set(key, value)); + }); + } + + @computed + get Bounds(): { x: number, y: number, b: number, r: number } { + let left = Number.MAX_VALUE; + let top = Number.MAX_VALUE; + let right = -Number.MAX_VALUE; + let bottom = -Number.MAX_VALUE; + this._selectedInkNodes.forEach((value: StrokeData, key: string) => { + value.pathData.map(val => { + left = Math.min(val.x, left); + top = Math.min(val.y, top); + right = Math.max(val.x, right); + bottom = Math.max(val.y, bottom); + }); + }); + return { x: left, y: top, b: bottom, r: right }; + } + + render() { + let bounds = this.Bounds; + return ( +
+ +
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0ede0b770..41de32f1f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -37,6 +37,7 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu'; +import InkSelectDecorations from './InkSelectDecorations'; @observer export class MainView extends React.Component { @@ -512,6 +513,7 @@ export class MainView extends React.Component { + {this.mainContent} diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx index 4955129ba..26779b168 100644 --- a/src/client/views/Touchable.tsx +++ b/src/client/views/Touchable.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { action } from 'mobx'; import { InteractionUtils } from '../util/InteractionUtils'; -export abstract class Touchable extends React.Component { +export abstract class Touchable extends React.Component { protected _touchDrag: boolean = false; protected prevPoints: Map = new Map(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 123941b03..c24e52aba 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -103,9 +103,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { added && this.updateCluster(newBox); return added; } - private selectDocuments = (docs: Doc[]) => { + private selectDocuments = (docs: Doc[], ink: Map[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); + ink.forEach(i => SelectionManager.SelectInk(i, true)); } public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } @@ -190,7 +191,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view let prevSelected = SelectionManager.SelectedDocuments(); - this.selectDocuments(eles); + this.selectDocuments(eles, []); let clusterDocs = SelectionManager.SelectedDocuments(); SelectionManager.DeselectAll(); prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 07db6354f..cd9ac7ecc 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -20,6 +20,7 @@ import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; +import InkSelectDecorations from "../../InkSelectDecorations"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -27,7 +28,7 @@ interface MarqueeViewProps { container: CollectionFreeFormView; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; - selectDocuments: (docs: Doc[]) => void; + selectDocuments: (docs: Doc[], ink: Map[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; @@ -190,13 +191,14 @@ export class MarqueeView extends React.Component @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document], []); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document], + this.ink ? [this.marqueeInkSelect(this.ink.inkData)] : []); } if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; @@ -350,7 +352,7 @@ export class MarqueeView extends React.Component } let newCollection = this.getCollection(selected); this.props.addDocument(newCollection); - this.props.selectDocuments([newCollection]); + this.props.selectDocuments([newCollection], []); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } @@ -422,9 +424,14 @@ export class MarqueeView extends React.Component let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2); ink.forEach((value: StrokeData, key: string, map: any) => { if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { + // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling()); idata.set(key, { - pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })), + pathData: value.pathData.map(val => { + let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y); + return { x: tVal[0], y: tVal[1] }; + // return { x: val.x + centerShiftX, y: val.y + centerShiftY } + }), color: value.color, width: value.width, tool: value.tool, @@ -432,6 +439,7 @@ export class MarqueeView extends React.Component }); } }); + // InkSelectDecorations.Instance.SetSelected(idata); return idata; } -- cgit v1.2.3-70-g09d2 From ca406ddce0a9b35ca9063f74979c952e080339f1 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 3 Nov 2019 16:59:13 -0500 Subject: some infrastructure stuff and bug fixes. not really any new features yet --- src/client/util/SelectionManager.ts | 13 +++++----- src/client/views/DocumentDecorations.tsx | 29 ++++++++++++++-------- src/client/views/InkingStroke.tsx | 6 +++++ .../views/collections/CollectionDockingView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 12 ++++++--- src/client/views/nodes/DocumentContentsView.tsx | 4 ++- 7 files changed, 46 insertions(+), 22 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 3ae43e029..2a57c67bd 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,6 +4,7 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; import { InkingControl } from "../views/InkingControl"; +import { InkDocAndStroke } from "../views/InkingStroke"; export namespace SelectionManager { @@ -11,7 +12,7 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; - @observable SelectedInk: Array> = []; + @observable SelectedInk: Array<{ Document: Doc, Ink: Map }> = []; @action @@ -46,7 +47,7 @@ export namespace SelectionManager { } @action - SelectInk(ink: Map, ctrlPressed: boolean): void { + SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { if (manager.SelectedInk.indexOf(ink) === -1) { if (!ctrlPressed) { this.DeselectAll(); @@ -68,7 +69,7 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - export function SelectInk(ink: Map, ctrlPressed: boolean): void { + export function SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { manager.SelectInk(ink, ctrlPressed); } @@ -95,12 +96,12 @@ export namespace SelectionManager { return manager.SelectedDocuments.slice(); } - export function SelectedInk(): Array> { + export function SelectedInk(): Array<{ Document: Doc, Ink: Map }> { return manager.SelectedInk.slice(); } - export function AllSelected(): Array> { - let arr: Array> = []; + export function AllSelected(): Array { + let arr: Array = []; arr = SelectionManager.SelectedDocuments(); arr.push(...SelectionManager.SelectedInk()); return arr; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cdb49350f..c3020eacb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -186,8 +186,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let top = bounds.y; let right = bounds.r; let bottom = bounds.b; - docViewOrInk.forEach((value: StrokeData, key: string) => { + let ink; + docViewOrInk.Ink.forEach((value: StrokeData, key: string) => { value.pathData.map(val => { + ink = docViewOrInk.Document.ink; left = Math.min(val.x, left); top = Math.min(val.y, top); right = Math.max(val.x, right); @@ -224,7 +226,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onBackgroundUp); document.removeEventListener("pointermove", this.onTitleMove); document.removeEventListener("pointerup", this.onTitleUp); - DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, { + DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, { handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, hideSource: true }); @@ -548,15 +550,21 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get selectionTitle(): string { - if (SelectionManager.SelectedDocuments().length === 1) { - let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey]; - if (typeof field === "string") { - return field; + if (SelectionManager.AllSelected().length === 1) { + let selected = SelectionManager.AllSelected()[0]; + if (selected instanceof DocumentView) { + let field = selected.props.Document[this._fieldKey]; + if (typeof field === "string") { + return field; + } + else if (typeof field === "number") { + return field.toString(); + } } - else if (typeof field === "number") { - return field.toString(); + else { + return "-ink strokes-"; } - } else if (SelectionManager.SelectedDocuments().length > 1) { + } else if (SelectionManager.AllSelected().length > 1) { return "-multiple-"; } return "-unset-"; @@ -581,7 +589,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } let minimizeIcon = (
- {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} + {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} + {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
); bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 332c22512..a097a7991 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -5,6 +5,7 @@ import React = require("react"); import { InkTool } from "../../new_fields/InkField"; import "./InkingStroke.scss"; import { AudioBox } from "./nodes/AudioBox"; +import { Doc } from "../../new_fields/Doc"; interface StrokeProps { @@ -20,6 +21,11 @@ interface StrokeProps { deleteCallback: (index: string) => void; } +export type InkDocAndStroke = { + Document: Doc; + Ink: Map; +}; + @observer export class InkingStroke extends React.Component { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 937a4949e..3ff99b9f4 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -617,7 +617,7 @@ export class DockedFrameRenderer extends React.Component { } } - get layoutDoc() { return this._document && Doc.Layout(this._document);} + get layoutDoc() { return this._document && Doc.Layout(this._document); } panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth; panelHeight = () => this._panelHeight; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5e2973368..9acffc952 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -107,7 +107,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { added && this.updateCluster(newBox); return added; } - private selectDocuments = (docs: Doc[], ink: Map[]) => { + private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); ink.forEach(i => SelectionManager.SelectInk(i, true)); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 414238ccd..138168fed 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,7 +28,7 @@ interface MarqueeViewProps { getTransform: () => Transform; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; - selectDocuments: (docs: Doc[], ink: Map[]) => void; + selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; @@ -198,8 +198,10 @@ export class MarqueeView extends React.Component 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; @@ -266,6 +268,10 @@ export class MarqueeView extends React.Component Date: Tue, 5 Nov 2019 16:56:04 -0500 Subject: fixes for batched array --- src/client/util/Import & Export/DirectoryImportBox.tsx | 6 +++--- src/server/apis/google/GooglePhotosUploadUtils.ts | 17 +++++++++-------- src/server/index.ts | 15 +++++++-------- 3 files changed, 19 insertions(+), 19 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index f27d05487..5904088fc 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -108,7 +108,8 @@ export default class DirectoryImportBox extends React.Component runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); - const uploads = await BatchedArray.from(validated, { batchSize: 15 }).batchedMapAsync(async batch => { + const batched = BatchedArray.from(validated, { batchSize: 15 }); + const uploads = await batched.batchedMapAsync(async (batch, collector) => { const formData = new FormData(); batch.forEach(file => { @@ -117,9 +118,8 @@ export default class DirectoryImportBox extends React.Component formData.append(Utils.GenerateGuid(), file); }); - const responses = await Identified.PostFormDataToServer(RouteStore.upload, formData); + collector.push(...(await Identified.PostFormDataToServer(RouteStore.upload, formData))); runInAction(() => this.completed += batch.length); - return responses as ImageUploadResponse[]; }); await Promise.all(uploads.map(async upload => { diff --git a/src/server/apis/google/GooglePhotosUploadUtils.ts b/src/server/apis/google/GooglePhotosUploadUtils.ts index 4a67e57cc..36256822c 100644 --- a/src/server/apis/google/GooglePhotosUploadUtils.ts +++ b/src/server/apis/google/GooglePhotosUploadUtils.ts @@ -1,7 +1,8 @@ + import request = require('request-promise'); import { GoogleApiServerUtils } from './GoogleApiServerUtils'; import * as path from 'path'; -import { MediaItemCreationResult } from './SharedTypes'; +import { MediaItemCreationResult, NewMediaItemResult } from './SharedTypes'; import { NewMediaItem } from "../../index"; import { BatchedArray, TimeUnit } from 'array-batcher'; import { DashUploadUtils } from '../../DashUploadUtils'; @@ -56,10 +57,11 @@ export namespace GooglePhotosUploadUtils { })); }; - export const CreateMediaItems = async (newMediaItems: NewMediaItem[], album?: { id: string }): Promise => { - const newMediaItemResults = await BatchedArray.from(newMediaItems, { batchSize: 50 }).batchedMapPatientInterval( + export const CreateMediaItems = async (newMediaItems: NewMediaItem[], album?: { id: string }): Promise => { + const batched = BatchedArray.from(newMediaItems, { batchSize: 50 }); + return batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: NewMediaItem[]) => { + async (batch, collector) => { const parameters = { method: 'POST', headers: headers('json'), @@ -68,18 +70,17 @@ export namespace GooglePhotosUploadUtils { json: true }; album && (parameters.body.albumId = album.id); - return (await new Promise((resolve, reject) => { + collector.push(...(await new Promise((resolve, reject) => { request(parameters, (error, _response, body) => { if (error) { reject(error); } else { - resolve(body); + resolve(body.newMediaItemResults); } }); - })).newMediaItemResults; + }))); } ); - return { newMediaItemResults }; }; } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index ee6a497ba..89b5085b1 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1049,23 +1049,22 @@ addSecureRoute({ let failed: number[] = []; - const newMediaItems = await BatchedArray.from(media, { batchSize: 25 }).batchedMapPatientInterval( + const batched = BatchedArray.from(media, { batchSize: 25 }); + const newMediaItems = await batched.batchedMapPatientInterval( { magnitude: 100, unit: TimeUnit.Milliseconds }, - async (batch: GooglePhotosUploadUtils.MediaInput[]) => { - const newMediaItems: NewMediaItem[] = []; + async (batch, collector) => { for (let index = 0; index < batch.length; index++) { - const element = batch[index]; - const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(element.url); + const { url, description } = batch[index]; + const uploadToken = await GooglePhotosUploadUtils.DispatchGooglePhotosUpload(url); if (!uploadToken) { failed.push(index); } else { - newMediaItems.push({ - description: element.description, + collector.push({ + description, simpleMediaItem: { uploadToken } }); } } - return newMediaItems; } ); -- cgit v1.2.3-70-g09d2 From c7c18eeea36b35ee9172a120352af84fe21f267b Mon Sep 17 00:00:00 2001 From: yipstanley Date: Sun, 10 Nov 2019 16:27:56 -0500 Subject: inks are now dox --- src/client/documents/Documents.ts | 14 ++- src/client/util/SelectionManager.ts | 32 ------ src/client/views/DocumentDecorations.tsx | 80 ++++++--------- src/client/views/InkSelectDecorations.tsx | 16 +-- src/client/views/InkingCanvas.tsx | 12 +-- src/client/views/InkingStroke.tsx | 107 +++++++++------------ .../collectionFreeForm/CollectionFreeFormView.tsx | 64 +++++++++--- .../collections/collectionFreeForm/MarqueeView.tsx | 92 +++++++++--------- src/client/views/nodes/DocumentView.tsx | 2 +- src/new_fields/InkField.ts | 20 ++-- src/new_fields/documentSchemas.ts | 1 + 11 files changed, 207 insertions(+), 233 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2c6b40cb9..a074d267e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox"; import { QueryBox } from "../views/nodes/QueryBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { DocuLinkBox } from "../views/nodes/DocuLinkBox"; +import { InkingStroke } from "../views/InkingStroke"; +import { InkField } from "../../new_fields/InkField"; var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -107,6 +109,7 @@ export interface DocumentOptions { sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script dropConverter?: ScriptField; // script to run when documents are dropped on this Document. + strokeWidth?: number; // [key: string]: Opt; } @@ -209,6 +212,9 @@ export namespace Docs { [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: data } }], + [DocumentType.INK, { + layout: { view: InkingStroke, dataField: data } + }] ]); // All document prototypes are initialized with at least these values @@ -411,8 +417,12 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options); } - export function InkDocument(options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.INK), "", options); + export function InkDocument(color: string, tool: number, strokeWidth: number, points: { x: number, y: number }[], options: DocumentOptions = {}) { + let doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options); + doc.color = color; + doc.strokeWidth = strokeWidth; + doc.tool = tool; + return doc; } export function IconDocument(icon: string, options: DocumentOptions = {}) { diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 2a57c67bd..ca61f9014 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -3,8 +3,6 @@ import { Doc, Opt } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { NumCast, StrCast } from "../../new_fields/Types"; -import { InkingControl } from "../views/InkingControl"; -import { InkDocAndStroke } from "../views/InkingStroke"; export namespace SelectionManager { @@ -12,7 +10,6 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; @observable SelectedDocuments: Array = []; - @observable SelectedInk: Array<{ Document: Doc, Ink: Map }> = []; @action @@ -43,20 +40,6 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; - manager.SelectedInk = []; - } - - @action - SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { - if (manager.SelectedInk.indexOf(ink) === -1) { - if (!ctrlPressed) { - this.DeselectAll(); - } - - manager.SelectedInk.push(ink); - } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { - manager.SelectedInk = [ink]; - } } } @@ -69,10 +52,6 @@ export namespace SelectionManager { manager.SelectDoc(docView, ctrlPressed); } - export function SelectInk(ink: { Document: Doc, Ink: Map }, ctrlPressed: boolean): void { - manager.SelectInk(ink, ctrlPressed); - } - export function IsSelected(doc: DocumentView): boolean { return manager.SelectedDocuments.indexOf(doc) !== -1; } @@ -95,15 +74,4 @@ export namespace SelectionManager { export function SelectedDocuments(): Array { return manager.SelectedDocuments.slice(); } - - export function SelectedInk(): Array<{ Document: Doc, Ink: Map }> { - return manager.SelectedInk.slice(); - } - - export function AllSelected(): Array { - let arr: Array = []; - arr = SelectionManager.SelectedDocuments(); - arr.push(...SelectionManager.SelectedInk()); - return arr; - } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index e208e5f3b..10764a9ce 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,7 +24,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); -import { StrokeData } from '../../new_fields/InkField'; +import { PointData } from '../../new_fields/InkField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -162,44 +162,23 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get Bounds(): { x: number, y: number, b: number, r: number } { let x = this._forceUpdate; - this._lastBox = SelectionManager.AllSelected().reduce((bounds, docViewOrInk) => { - if (docViewOrInk instanceof DocumentView) { - if (docViewOrInk.props.renderDepth === 0 || - Doc.AreProtosEqual(docViewOrInk.props.Document, CurrentUserUtils.UserDocument)) { - return bounds; - } - let transform = (docViewOrInk.props.ScreenToLocalTransform().scale(docViewOrInk.props.ContentScaling())).inverse(); - if (transform.TranslateX === 0 && transform.TranslateY === 0) { - setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds - return this._lastBox; - } - - var [sptX, sptY] = transform.transformPoint(0, 0); - let [bptX, bptY] = transform.transformPoint(docViewOrInk.props.PanelWidth(), docViewOrInk.props.PanelHeight()); - return { - x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), - r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) - }; + this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, docView) => { + if (docView.props.renderDepth === 0 || + Doc.AreProtosEqual(docView.props.Document, CurrentUserUtils.UserDocument)) { + return bounds; } - else { - let left = bounds.x; - let top = bounds.y; - let right = bounds.r; - let bottom = bounds.b; - let ink; - docViewOrInk.Ink.forEach((value: StrokeData, key: string) => { - value.pathData.map(val => { - ink = docViewOrInk.Document.ink; - left = Math.min(val.x, left); - top = Math.min(val.y, top); - right = Math.max(val.x, right); - bottom = Math.max(val.y, bottom); - }); - }); - return { - x: left, y: top, r: right, b: bottom - }; + let transform = (docView.props.ScreenToLocalTransform().scale(docView.props.ContentScaling())).inverse(); + if (transform.TranslateX === 0 && transform.TranslateY === 0) { + setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds + return this._lastBox; } + + var [sptX, sptY] = transform.transformPoint(0, 0); + let [bptX, bptY] = transform.transformPoint(docView.props.PanelWidth(), docView.props.PanelHeight()); + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); return this._lastBox; } @@ -226,7 +205,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onBackgroundUp); document.removeEventListener("pointermove", this.onTitleMove); document.removeEventListener("pointerup", this.onTitleUp); - DragManager.StartDocumentDrag(SelectionManager.AllSelected().map(docOrInk => docOrInk instanceof DocumentView ? docOrInk.ContentDiv! : (document.createElement("div"))), dragData, e.x, e.y, { + DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, { handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, hideSource: true }); @@ -550,21 +529,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> @computed get selectionTitle(): string { - if (SelectionManager.AllSelected().length === 1) { - let selected = SelectionManager.AllSelected()[0]; - if (selected instanceof DocumentView) { - let field = selected.props.Document[this._fieldKey]; - if (typeof field === "string") { - return field; - } - else if (typeof field === "number") { - return field.toString(); - } + if (SelectionManager.SelectedDocuments().length === 1) { + let selected = SelectionManager.SelectedDocuments()[0]; + let field = selected.props.Document[this._fieldKey]; + if (typeof field === "string") { + return field; } - else { - return "-ink strokes-"; + else if (typeof field === "number") { + return field.toString(); } - } else if (SelectionManager.AllSelected().length > 1) { + } else if (SelectionManager.SelectedDocuments().length > 1) { return "-multiple-"; } return "-unset-"; @@ -590,7 +564,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let minimizeIcon = (
{/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} - {(SelectionManager.SelectedDocuments().length === 1 && SelectionManager.SelectedInk().length === 0) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."} + {(SelectionManager.SelectedDocuments().length === 1) ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
); bounds.x = Math.max(0, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; @@ -611,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, pointerEvents: this.Interacting ? "none" : "all", - zIndex: SelectionManager.AllSelected().length > 1 ? 900 : 0, + zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
{ - value.pathData.map(val => { - left = Math.min(val.x, left); - top = Math.min(val.y, top); - right = Math.max(val.x, right); - bottom = Math.max(val.y, bottom); - }); + this._selectedInkNodes.forEach((value: PointData, key: string) => { + // value.pathData.map(val => { + // left = Math.min(val.x, left); + // top = Math.min(val.y, top); + // right = Math.max(val.x, right); + // bottom = Math.max(val.y, bottom); + // }); }); return { x: left, y: top, b: bottom, r: right }; } diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index a0ea37300..e5253c377 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -7,7 +7,7 @@ import { InkingControl } from "./InkingControl"; import { InkingStroke } from "./InkingStroke"; import React = require("react"); import { UndoManager } from "../util/UndoManager"; -import { StrokeData, InkField, InkTool } from "../../new_fields/InkField"; +import { PointData, InkField, InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { Cast, PromiseValue, NumCast } from "../../new_fields/Types"; import { Touchable } from "./Touchable"; @@ -26,15 +26,15 @@ export class InkingCanvas extends Touchable { maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome @observable inkMidX: number = 0; @observable inkMidY: number = 0; - private previousState?: Map; + private previousState?: Map; private _currentStrokeId: string = ""; - public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean { + public static IntersectStrokeRect(stroke: PointData, selRect: { left: number, top: number, width: number, height: number }): boolean { return stroke.pathData.reduce((inside: boolean, val) => inside || (selRect.left < val.x && selRect.left + selRect.width > val.x && selRect.top < val.y && selRect.top + selRect.height > val.y) , false); } - public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } { + public static StrokeRect(stroke: PointData): { left: number, top: number, right: number, bottom: number } { return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) => ({ left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y), @@ -58,12 +58,12 @@ export class InkingCanvas extends Touchable { } @computed - get inkData(): Map { + get inkData(): Map { let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField); return !map ? new Map : new Map(map.inkData); } - set inkData(value: Map) { + set inkData(value: Map) { this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value); } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 824f40b1f..411b0d3a0 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,79 +1,66 @@ import { observer } from "mobx-react"; -import { observable, trace, runInAction } from "mobx"; +import { observable, trace, runInAction, computed } from "mobx"; import { InkingControl } from "./InkingControl"; import React = require("react"); -import { InkTool } from "../../new_fields/InkField"; +import { InkTool, InkField, InkData } from "../../new_fields/InkField"; import "./InkingStroke.scss"; import { AudioBox } from "./nodes/AudioBox"; -import { Doc } from "../../new_fields/Doc"; -import { createSchema, makeInterface } from "../../new_fields/Schema"; +import { Doc, FieldResult } from "../../new_fields/Doc"; +import { createSchema, makeInterface, listSpec } from "../../new_fields/Schema"; import { documentSchema } from "../../new_fields/documentSchemas"; import { DocExtendableComponent } from "./DocComponent"; import { FieldViewProps, FieldView } from "./nodes/FieldView"; - - -interface StrokeProps { - offsetX: number; - offsetY: number; - id: string; - count: number; - line: Array<{ x: number, y: number }>; - color: string; - width: string; - tool: InkTool; - creationTime: number; - deleteCallback: (index: string) => void; -} +import { Transform } from "../util/Transform"; +import { Cast, FieldValue } from "../../new_fields/Types"; +import { List } from "../../new_fields/List"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); +export function CreatePolyline(points: { x: number, y: number }[], left: number, top: number, color?: string, width?: number) { + let pts = points.reduce((acc: string, pt: { x: number, y: number }) => acc + `${pt.x - left},${pt.y - top} `, ""); + return ( + + ); +} + @observer -export class InkingStroke extends DocExtendableComponent(InkDocument) { +export class InkingStroke extends DocExtendableComponent(InkDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); } - @observable private _strokeTool: InkTool = this.props.tool; - @observable private _strokeColor: string = this.props.color; - @observable private _strokeWidth: string = this.props.width; - - deleteStroke = (e: React.PointerEvent): void => { - if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) { - this.props.deleteCallback(this.props.id); - e.stopPropagation(); - e.preventDefault(); - } - if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) { - AudioBox.SetScrubTime(this.props.creationTime); - e.stopPropagation(); - e.preventDefault(); - } - } - - parseData = (line: Array<{ x: number, y: number }>): string => { - return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L "); - } - - createStyle() { - switch (this._strokeTool) { - // add more tool styles here - default: - return { - fill: "none", - stroke: this._strokeColor, - strokeWidth: this._strokeWidth + "px", - }; - } - } + @computed get PanelWidth() { return this.props.PanelWidth(); } + @computed get PanelHeight() { return this.props.PanelHeight(); } render() { - let pathStyle = this.createStyle(); - let pathData = this.parseData(this.props.line); - let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes - let marker = this.props.tool === InkTool.Highlighter ? "-marker" : ""; - - let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser || - InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none"; - return (); + // let pathData = this.parseData(this.props.line); + let data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? []; + let xs = data.map(p => p.x); + let ys = data.map(p => p.y); + let left = Math.min(...xs); + let top = Math.min(...ys); + let right = Math.max(...xs); + let bottom = Math.max(...ys); + let points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth); + let width = right - left; + let height = bottom - top; + let scaleX = this.PanelWidth / width; + let scaleY = this.PanelHeight / height; + // let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes + return ( + + {points} + + ); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8294eaaec..21981c25e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkField, StrokeData, InkTool } from "../../../../new_fields/InkField"; +import { InkField, PointData, InkTool } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; @@ -40,6 +40,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import PDFMenu from "../../pdf/PDFMenu"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; import { InkingControl } from "../../InkingControl"; +import { InkingStroke, CreatePolyline } from "../../InkingStroke"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -108,10 +109,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { added && this.updateCluster(newBox); return added; } - private selectDocuments = (docs: Doc[], ink: { Document: Doc, Ink: Map }[]) => { + private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); - ink.forEach(i => SelectionManager.SelectInk(i, true)); } public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } @@ -284,14 +284,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { e.preventDefault(); if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - this._points.push({ x: e.pageX, y: e.pageY }); + let point = this.getTransform().transformPoint(e.pageX, e.pageY); + this._points.push({ x: point[0], y: point[1] }); } } } } + @action onPointerUp = (e: PointerEvent): void => { if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) return; + + if (this._points.length > 1) { + let B = this.svgBounds; + let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top })); + let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top }); + this.addDocument(inkDoc); + this._points = []; + } + document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.removeEventListener("touchmove", this.onTouch); @@ -324,11 +335,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { }, [[minx, maxx], [miny, maxy]]); let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField); if (ink && ink.inkData) { - ink.inkData.forEach((value: StrokeData, key: string) => { - let bounds = InkingCanvas.StrokeRect(value); - ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; - ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; - }); + // ink.inkData.forEach((value: PointData, key: string) => { + // let bounds = InkingCanvas.StrokeRect(value); + // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; + // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; + // }); } let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; @@ -363,8 +374,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.pan(e); } - if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - this._points.push({ x: e.clientX, y: e.clientY }); + else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { + let point = this.getTransform().transformPoint(e.clientX, e.clientY); + this._points.push({ x: point[0], y: point[1] }); } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); @@ -470,7 +482,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } else if (this.props.active()) { e.stopPropagation(); - this.zoom(e.clientX, e.clientY, e.deltaY) + this.zoom(e.clientX, e.clientY, e.deltaY); } } @@ -790,6 +802,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ...this.views, ]; } + + @computed get svgBounds() { + let xs = this._points.map(p => p.x); + let ys = this._points.map(p => p.y); + let right = Math.max(...xs); + let left = Math.min(...xs); + let bottom = Math.max(...ys); + let top = Math.min(...ys); + return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; + } + + @computed get currentStroke() { + if (this._points.length <= 1) { + return (null); + } + + let B = this.svgBounds; + + return ( + + {CreatePolyline(this._points, B.left, B.top)} + + ); + } + render() { // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) this.Document.fitX = this.contentBounds && this.contentBounds.x; @@ -808,9 +845,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> {!this.extensionDoc ? (null) : // - this.childViews + this.childViews() // } + {this.currentStroke} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 138168fed..b5f6f095e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,7 +1,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../../new_fields/Doc"; -import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { InkField, PointData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { listSpec } from "../../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; @@ -198,10 +198,10 @@ export class MarqueeView extends React.Component 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; @@ -217,7 +217,7 @@ export class MarqueeView extends React.Component { this.marqueeSelect(false).map(d => this.props.removeDocument(d)); if (this.ink) { - this.marqueeInkDelete(this.ink.inkData); + // this.marqueeInkDelete(this.ink.inkData); } SelectionManager.DeselectAll(); this.cleanupInteractions(false); @@ -336,8 +336,8 @@ export class MarqueeView extends React.Component) { - let idata = new Map(); - let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin. - let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2); - ink.forEach((value: StrokeData, key: string, map: any) => { - if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { - // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling()); - idata.set(key, - { - pathData: value.pathData.map(val => { - let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y); - return { x: tVal[0], y: tVal[1] }; - // return { x: val.x + centerShiftX, y: val.y + centerShiftY } - }), - color: value.color, - width: value.width, - tool: value.tool, - page: -1 - }); - } - }); - // InkSelectDecorations.Instance.SetSelected(idata); - return idata; - } + // @action + // marqueeInkSelect(ink: Map) { + // let idata = new Map(); + // let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin. + // let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2); + // ink.forEach((value: PointData, key: string, map: any) => { + // if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { + // // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling()); + // idata.set(key, + // { + // pathData: value.pathData.map(val => { + // let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y); + // return { x: tVal[0], y: tVal[1] }; + // // return { x: val.x + centerShiftX, y: val.y + centerShiftY } + // }), + // color: value.color, + // width: value.width, + // tool: value.tool, + // page: -1 + // }); + // } + // }); + // // InkSelectDecorations.Instance.SetSelected(idata); + // return idata; + // } - @action - marqueeInkDelete(ink?: Map) { - // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB. - // ink.forEach((value: StrokeData, key: string, map: any) => - // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key)); + // @action + // marqueeInkDelete(ink?: Map) { + // // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB. + // // ink.forEach((value: StrokeData, key: string, map: any) => + // // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key)); - if (ink) { - let idata = new Map(); - ink.forEach((value: StrokeData, key: string, map: any) => - !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); - this.ink = new InkField(idata); - } - } + // if (ink) { + // let idata = new Map(); + // ink.forEach((value: PointData, key: string, map: any) => + // !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); + // this.ink = new InkField(idata); + // } + // } marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 62529a5fb..5c89472ce 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -632,7 +632,7 @@ export class DocumentView extends DocComponent(Docu {searchHighlight}
} - + ; } render() { if (!this.props.Document) return (null); diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts index d94834e91..2d8bb582a 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -12,16 +12,12 @@ export enum InkTool { Scrubber } -export interface StrokeData { - pathData: Array<{ x: number, y: number }>; - color: string; - width: string; - tool: InkTool; - displayTimecode: number; - creationTime: number; +export interface PointData { + x: number; + y: number; } -export type InkData = Map; +export type InkData = Array; const pointSchema = createSimpleSchema({ x: true, y: true @@ -34,16 +30,16 @@ const strokeDataSchema = createSimpleSchema({ @Deserializable("ink") export class InkField extends ObjectField { - @serializable(map(object(strokeDataSchema))) + @serializable(list(object(strokeDataSchema))) readonly inkData: InkData; - constructor(data?: InkData) { + constructor(data: InkData) { super(); - this.inkData = data || new Map; + this.inkData = data; } [Copy]() { - return new InkField(DeepCopy(this.inkData)); + return new InkField(this.inkData); } [ToScriptString]() { diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e2730914f..0b28561bf 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -43,6 +43,7 @@ export const documentSchema = createSchema({ isAnimating: "boolean", // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents). animateToDimensions: listSpec("number"), // layout information about the target rectangle a document is animating towards scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc. + strokeWidth: "number" }); export const positionSchema = createSchema({ -- cgit v1.2.3-70-g09d2 From b625719e71bcfc09e905b8f164b3ed76994b1d59 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 10 Nov 2019 19:16:21 -0500 Subject: fixed changing bullet type --- src/client/util/TooltipTextMenu.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 31d98887f..b1bd73e54 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -531,8 +531,9 @@ export class TooltipTextMenu { } //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { + changeToNodeType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new + let view = this.view; if (nodeType === schema.nodes.bullet_list) { wrapInList(nodeType)(view.state, view.dispatch); } else { -- cgit v1.2.3-70-g09d2 From f4ca517725714d770096d683478a6f5cc995c0c3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 10 Nov 2019 19:46:32 -0500 Subject: fixed switching between ordered list and bullets. --- src/client/util/RichTextSchema.tsx | 3 ++- src/client/util/TooltipTextMenu.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index cc3548e1a..a224c57eb 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -241,6 +241,7 @@ export const nodes: { [index: string]: NodeSpec } = { }, toDOM(node: Node) { const bs = node.attrs.bulletStyle; + if (node.attrs.mapStyle === "bullet") return ['ul', 0]; const decMap = bs ? "decimal" + bs : ""; const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; @@ -273,7 +274,7 @@ export const nodes: { [index: string]: NodeSpec } = { const bs = node.attrs.bulletStyle; const decMap = bs ? "decimal" + bs : ""; const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; - let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; + let map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : ""; return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; //return ["li", { class: `${map}` }, 0]; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index b1bd73e54..5cbf8b761 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -171,7 +171,8 @@ export class TooltipTextMenu { //list types this.listTypeToIcon = new Map(); - this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + //this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); @@ -539,14 +540,14 @@ export class TooltipTextMenu { } else { var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); view.dispatch(tx2); })) { let tx2 = view.state.tr; - let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; + let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); -- cgit v1.2.3-70-g09d2 From d220b23867050fb6565fe639c3be28080f589aa2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 Nov 2019 18:39:23 -0500 Subject: made font size and font style text marks be parametrized to simplify api. --- src/client/util/RichTextSchema.tsx | 154 ++-------------- src/client/util/TooltipTextMenu.tsx | 277 +++++++++++++++++----------- src/client/views/nodes/FormattedTextBox.tsx | 15 +- 3 files changed, 190 insertions(+), 256 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a224c57eb..a5e0ca720 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -483,72 +483,26 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return codeDOM; } }, - // pFontFamily: { - // attrs: { - // style: { default: 'font-family: "Times New Roman", Times, serif;' }, - // }, - // parseDOM: [{ - // tag: "span", getAttrs(dom: any) { - // if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` }; - // if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` }; - // if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` }; - // if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` }; - // if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` }; - // } - // }], - // toDOM: (node: any) => ['span', { - // style: node.attrs.style - // }] - // }, - - /* FONTS */ - timesNewRoman: { - parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Times New Roman", Times, serif;' - }] - }, - - arial: { - parseDOM: [{ style: 'font-family: Arial, Helvetica, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: Arial, Helvetica, sans-serif;' - }] - }, - - georgia: { - parseDOM: [{ style: 'font-family: Georgia, serif;' }], - toDOM: () => ['span', { - style: 'font-family: Georgia, serif;' - }] - }, - - comicSans: { - parseDOM: [{ style: 'font-family: "Comic Sans MS", cursive, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Comic Sans MS", cursive, sans-serif;' - }] - }, - - tahoma: { - parseDOM: [{ style: 'font-family: Tahoma, Geneva, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: Tahoma, Geneva, sans-serif;' - }] - }, - - impact: { - parseDOM: [{ style: 'font-family: Impact, Charcoal, sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: Impact, Charcoal, sans-serif;' - }] - }, - - crimson: { - parseDOM: [{ style: 'font-family: "Crimson Text", sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Crimson Text", sans-serif;' + pFontFamily: { + attrs: { + family: { default: "Crimson Text" }, + }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + let cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; + if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; + if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; + if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; + if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; + if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; + } + } + }], + toDOM: (node) => ['span', { + style: `font-family: "${node.attrs.family}";` }] }, @@ -574,76 +528,6 @@ export const marks: { [index: string]: MarkSpec } = { style: `font-size: ${node.attrs.fontSize}px;` }] }, - - p10: { - parseDOM: [{ style: 'font-size: 10px;' }], - toDOM: () => ['span', { - style: 'font-size: 10px;' - }] - }, - - p12: { - parseDOM: [{ style: 'font-size: 12px;' }], - toDOM: () => ['span', { - style: 'font-size: 12px;' - }] - }, - - p14: { - parseDOM: [{ style: 'font-size: 14px;' }], - toDOM: () => ['span', { - style: 'font-size: 14px;' - }] - }, - - p16: { - parseDOM: [{ style: 'font-size: 16px;' }], - toDOM: () => ['span', { - style: 'font-size: 16px;' - }] - }, - - p18: { - parseDOM: [{ style: 'font-size: 18px;' }], - toDOM: () => ['span', { - style: 'font-size: 18px;' - }] - }, - - p20: { - parseDOM: [{ style: 'font-size: 20px;' }], - toDOM: () => ['span', { - style: 'font-size: 20px;' - }] - }, - - p24: { - parseDOM: [{ style: 'font-size: 24px;' }], - toDOM: () => ['span', { - style: 'font-size: 24px;' - }] - }, - - p32: { - parseDOM: [{ style: 'font-size: 32px;' }], - toDOM: () => ['span', { - style: 'font-size: 32px;' - }] - }, - - p48: { - parseDOM: [{ style: 'font-size: 48px;' }], - toDOM: () => ['span', { - style: 'font-size: 48px;' - }] - }, - - p72: { - parseDOM: [{ style: 'font-size: 72px;' }], - toDOM: () => ['span', { - style: 'font-size: 72px;' - }] - }, }; export class ImageResizeView { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 5cbf8b761..9ce7acec8 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -29,11 +29,9 @@ export class TooltipTextMenu { public tooltip: HTMLElement; private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private fontStyles: MarkType[]; - private fontSizes: MarkType[]; private listTypes: (NodeType | any)[]; - private fontSizeToNum: Map; - private fontStylesToName: Map; + private fontSizes: Mark[] = []; + private fontStyles: Mark[] = []; private listTypeToIcon: Map; //private link: HTMLAnchorElement; private wrapper: HTMLDivElement; @@ -135,46 +133,32 @@ export class TooltipTextMenu { //list of font styles - this.fontStylesToName = new Map(); - 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 MS"); - this.fontStylesToName.set(schema.marks.tahoma, "Tahoma"); - this.fontStylesToName.set(schema.marks.impact, "Impact"); - this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); - this.fontStyles = Array.from(this.fontStylesToName.keys()); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" })); //font sizes - this.fontSizeToNum = new Map(); - this.fontSizeToNum.set(schema.marks.p10, 10); - this.fontSizeToNum.set(schema.marks.p12, 12); - this.fontSizeToNum.set(schema.marks.p14, 14); - this.fontSizeToNum.set(schema.marks.p16, 16); - this.fontSizeToNum.set(schema.marks.p18, 18); - this.fontSizeToNum.set(schema.marks.p20, 20); - this.fontSizeToNum.set(schema.marks.p24, 24); - this.fontSizeToNum.set(schema.marks.p32, 32); - this.fontSizeToNum.set(schema.marks.p48, 48); - this.fontSizeToNum.set(schema.marks.p72, 72); - this.fontSizeToNum.set(schema.marks.pFontSize, 10); - // this.fontSizeToNum.set(schema.marks.pFontSize, 12); - // this.fontSizeToNum.set(schema.marks.pFontSize, 14); - // this.fontSizeToNum.set(schema.marks.pFontSize, 16); - // this.fontSizeToNum.set(schema.marks.pFontSize, 18); - // this.fontSizeToNum.set(schema.marks.pFontSize, 20); - // this.fontSizeToNum.set(schema.marks.pFontSize, 24); - // this.fontSizeToNum.set(schema.marks.pFontSize, 32); - // this.fontSizeToNum.set(schema.marks.pFontSize, 48); - // this.fontSizeToNum.set(schema.marks.pFontSize, 72); - this.fontSizes = Array.from(this.fontSizeToNum.keys()); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 16 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 18 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 20 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 24 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 })); //list types this.listTypeToIcon = new Map(); //this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":"); - this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); - this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1)"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A)"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); @@ -195,16 +179,13 @@ export class TooltipTextMenu { //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 SIZES let fontSizeBtns: MenuItem[] = []; - this.fontSizeToNum.forEach((number, mark) => { - fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); + this.fontSizes.forEach(mark => { + fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize)); }); - let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { + let newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" }) as MenuItem).render(this.view).dom; @@ -215,20 +196,15 @@ export class TooltipTextMenu { this.fontSizeDom = newfontSizeDom; } - // Make the DIV element draggable - //label of dropdown will change to given label updateFontStyleDropdown(label: string) { - //filtering function - might be unecessary - let cut = (arr: MenuItem[]) => arr.filter(x => x); - //font STYLES let fontBtns: MenuItem[] = []; - this.fontStylesToName.forEach((name, mark) => { - fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + this.fontStyles.forEach((mark) => { + fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily)); }); - let newfontStyleDom = (new Dropdown(cut(fontBtns), { + let newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" }) as MenuItem).render(this.view).dom; @@ -468,8 +444,7 @@ export class TooltipTextMenu { this.tooltip.appendChild(listTypeBtn); return listTypeBtn; } - - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { let { $cursor, ranges } = view.state.selection as TextSelection; let state = view.state; @@ -498,29 +473,6 @@ export class TooltipTextMenu { }); if (markType) { - // fontsize - if (markType.name[0] === 'p') { - let size = this.fontSizeToNum.get(markType); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - } - else { - let fontName = this.fontStylesToName.get(markType); - if (fontName) { this.updateFontStyleDropdown(fontName); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - } //actually apply font if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, @@ -531,6 +483,90 @@ export class TooltipTextMenu { } } + changeToFontFamily = (mark: Mark, view: EditorView) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + if ($cursor) { + if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily); + } + for (let i of ranges) { + if (has) { + toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view); + } + } + } + + let fontName = mark.attrs.family; + if (fontName) { this.updateFontStyleDropdown(fontName); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName }))); + view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })]; + } + + changeToFontSize = (mark: Mark, view: EditorView) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + if ($cursor) { + if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize); + } + for (let i of ranges) { + if (has) { + toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view); + } + } + } + + let size = mark.attrs.fontSize; + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size }))); + view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })]; + } + //remove all node typeand apply the passed-in one to the selected text changeToNodeType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new @@ -558,7 +594,7 @@ export class TooltipTextMenu { //makes a button for the drop down FOR MARKS //css is the style you want applied to the button - dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark, view: EditorView) => any) { return new MenuItem({ title: "", label: label, @@ -567,7 +603,22 @@ export class TooltipTextMenu { css: css, enable() { return true; }, run() { - changeToMarkInGroup(markType, view, groupMarks); + changeFontFamily(mark, view); + } + }); + } + //makes a button for the drop down FOR MARKS + //css is the style you want applied to the button + dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark, view: EditorView) => any) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "menuicon", + css: css, + enable() { return true; }, + run() { + changeFontSize(mark, view); } }); } @@ -649,7 +700,9 @@ export class TooltipTextMenu { this.view.dispatch(this.view.state.tr.removeMark(from, to)); Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; - this.changeToMarkInGroup(markType, this.view, []); + if (mark.type === schema.marks.pFontFamily) this.changeToFontFamily(mark, this.view); + else if (mark.type === schema.marks.pFontSize) this.changeToFontSize(mark, this.view); + else this.changeToMarkInGroup(markType, this.view, []); }); } } @@ -868,50 +921,23 @@ export class TooltipTextMenu { //UPDATE LIST ITEM DROPDOWN //UPDATE FONT STYLE DROPDOWN - let activeStyles = this.activeMarksOnSelection(this.fontStyles); + let activeStyles = this.activeFontFamilyOnSelection(); if (activeStyles !== undefined) { - // activeStyles.forEach((markType) => { - // this._activeMarks.push(this.view.state.schema.mark(markType)); - // }); 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"); - } + activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]); + } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default"); } //UPDATE FONT SIZE DROPDOWN - let activeSizes = this.activeMarksOnSelection(this.fontSizes); + let activeSizes = this.activeFontSizeOnSelection(); if (activeSizes !== undefined) { if (activeSizes.length === 1) { //if there's only one active font size - // activeSizes.forEach((markType) => { - // this._activeMarks.push(this.view.state.schema.mark(markType)); - // }); - let size = this.fontSizeToNum.get(activeSizes[0]); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - } else if (activeSizes.length === 0) { - //should be 14 on default - this.updateFontSizeDropdown("14 pt"); - } else { //multiple font sizes selected - this.updateFontSizeDropdown("Various"); - } + activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt"); + } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default"); } this.update_mark_doms(); } - - public mark_key_pressed(marks: Mark[]) { - if (this.view.state.selection.empty) { - if (marks) this._activeMarks = marks; - this.update_mark_doms(); - } - } - update_mark_doms() { this.reset_mark_doms(); let foundlink = false; @@ -946,6 +972,30 @@ export class TooltipTextMenu { } + //finds fontSize at start of selection + activeFontSizeOnSelection() { + //current selection + let state = this.view.state; + let activeSizes: number[] = []; + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize)); + } + return activeSizes; + } + //finds fontSize at start of selection + activeFontFamilyOnSelection() { + //current selection + let state = this.view.state; + let activeFamilies: string[] = []; + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family)); + } + return activeFamilies; + } //finds all active marks on selection in given group activeMarksOnSelection(markGroup: MarkType[]) { //current selection @@ -980,6 +1030,9 @@ export class TooltipTextMenu { this._activeMarks = ref_node.marks; activeMarks = markGroup.filter(mark_type => { if (dispatch) { + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + } let mark = state.schema.mark(mark_type); return ref_node.marks.includes(mark); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 037c92a62..abf26826c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -93,8 +93,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F private _pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; - @observable private _fontSize = 13; - @observable private _fontFamily = "Arial"; + @observable private _ruleFontSize = 0; + @observable private _ruleFontFamily = "Arial"; @observable private _fontAlign = ""; @observable private _entered = false; public static SelectOnLoad = ""; @@ -187,9 +187,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { - FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); - } let tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); @@ -552,8 +549,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F return undefined; }, action((rules: any) => { - this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13); + this._ruleFontFamily = rules ? rules.font : "Arial"; + this._ruleFontSize = rules ? rules.size : 0; rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { @@ -1035,8 +1032,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive, - fontSize: this._fontSize, - fontFamily: this._fontFamily, + fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13), + fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"), }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} -- cgit v1.2.3-70-g09d2 From d7150995d62c498ab8435de986b90d98bdca020c Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 12 Nov 2019 15:07:03 -0500 Subject: a bunch of cleanup and bug fixes to text links and doculink buttons --- src/client/documents/Documents.ts | 5 +- src/client/util/LinkManager.ts | 2 - src/client/util/RichTextSchema.tsx | 4 +- src/client/util/TooltipTextMenu.tsx | 23 +++--- src/client/views/DocumentDecorations.tsx | 10 ++- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/Main.tsx | 24 ------- src/client/views/MainView.scss | 2 +- src/client/views/MainView.tsx | 8 +-- src/client/views/linking/LinkMenuItem.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 9 ++- src/client/views/nodes/FormattedTextBoxComment.tsx | 83 ++++++++++++---------- src/client/views/nodes/PDFBox.scss | 2 + src/client/views/nodes/PDFBox.tsx | 76 +++++++++++--------- src/client/views/pdf/Annotation.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 14 +--- src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 1 - 18 files changed, 127 insertions(+), 147 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d1fcabc4a..ba9f87025 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -257,6 +257,9 @@ export namespace Docs { return PrototypeMap.get(type)!; } + /** + * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. + */ export function MainLinkDocument() { return Prototypes.get(DocumentType.LINKDOC); } @@ -703,6 +706,7 @@ export namespace DocUtils { linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; linkDocProto.linkDescription = description; + linkDocProto.isPrototype = true; linkDocProto.anchor1 = source.doc; linkDocProto.anchor2 = target.doc; @@ -714,7 +718,6 @@ export namespace DocUtils { linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); - linkDocProto.borderRounding = "20"; linkDocProto.width = linkDocProto.height = 0; linkDocProto.isBackground = true; linkDocProto.isButton = true; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index ee2f2dadc..eedc4967d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -34,8 +34,6 @@ export class LinkManager { // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes' // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type public get LinkManagerDoc(): Doc | undefined { - // return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc)); - return Docs.Prototypes.MainLinkDocument(); } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5e0ca720..76b8aeaa1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -305,8 +305,8 @@ export const marks: { [index: string]: MarkSpec } = { }], toDOM(node: any) { return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - ["a", { ...node.attrs }, 0]; + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9ce7acec8..38471a955 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -17,7 +17,7 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; const { toggleMark, setBlockType } = require("prosemirror-commands"); @@ -284,7 +284,7 @@ export class TooltipTextMenu { if (proto && docView) { proto.sourceContext = docView.props.ContainingCollectionDoc; } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + let text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab"); if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link } @@ -374,25 +374,20 @@ export class TooltipTextMenu { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } - makeLink = (targetDoc: Doc, location: string): string => { - let target = Utils.prepend("/doc/" + targetDoc[Id]); + makeLink = (targetDoc: Doc, title: string, location: string): string => { + let link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). + addMark(this.view.state.selection.from, this.view.state.selection.to, link)); let node = this.view.state.selection.$from.nodeAfter; - let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); - this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - node = this.view.state.selection.$from.nodeAfter; - link = node && node.marks.find(m => m.type.name === "link"); - if (node) { - if (node.text) { - return node.text; - } + if (node && node.text) { + return node.text; } return ""; } deleteLink = () => { let node = this.view.state.selection.$from.nodeAfter; - let link = node && node.marks.find(m => m.type.name === "link"); + let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link); let href = link!.attrs.href; if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 0336440d5..55c211d1d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); +import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -112,7 +113,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { if (SelectionManager.SelectedDocuments().length > 0) { - SelectionManager.SelectedDocuments()[0].props.Document.customTitle = true; + SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._title.startsWith("-"); let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey]; if (typeof field === "number") { SelectionManager.SelectedDocuments().forEach(d => { @@ -174,6 +175,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> var [sptX, sptY] = transform.transformPoint(0, 0); let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight()); + if (documentView.props.Document.type === DocumentType.LINK) { + let rect = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont")[0].getBoundingClientRect(); + sptX = rect.left; + sptY = rect.top; + bptX = rect.right; + bptY = rect.bottom; + } return { x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 9ca9fc163..8f397e331 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -165,7 +165,7 @@ export default class KeyManager { } } break; - case "c": + case "t": PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); if (MainView.Instance.flyoutWidth === 240) { MainView.Instance.flyoutWidth = 0; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index a91a2b69e..b21eb9c8f 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -3,34 +3,11 @@ import { Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import * as ReactDOM from 'react-dom'; import * as React from 'react'; -import { Cast } from "../../new_fields/Types"; -import { Doc, DocListCastAsync } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; import { DocServer } from "../DocServer"; import { AssignAllExtensions } from "../../extensions/General/Extensions"; AssignAllExtensions(); -let swapDocs = async () => { - let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); - // Docs.Prototypes.MainLinkDocument().allLinks = new List(); - if (oldDoc) { - let links = await DocListCastAsync(oldDoc.allLinks); - // if (links && DocListCast(links)) { - if (links && links.length) { - let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks); - if (data) { - data.push(...links.filter(i => data!.indexOf(i) === -1)); - Docs.Prototypes.MainLinkDocument().allLinks = new List(data.filter((i, idx) => data!.indexOf(i) === idx)); - } - else { - Docs.Prototypes.MainLinkDocument().allLinks = new List(links); - } - } - CurrentUserUtils.UserDocument.linkManagerDoc = undefined; - } -}; - (async () => { const info = await CurrentUserUtils.loadCurrentUser(); DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email); @@ -38,7 +15,6 @@ let swapDocs = async () => { if (info.id !== "__guest__") { // a guest will not have an id registered await CurrentUserUtils.loadUserDocument(info); - await swapDocs(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 21b135c49..a858a73c7 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -13,7 +13,7 @@ left: 250px; } -.mainView-container { +#mainView-container { width: 100%; height: 100%; position: absolute; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 773da05df..83dbb433b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -37,7 +37,6 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { Scripting } from '../util/Scripting'; -import { LinkManager } from '../util/LinkManager'; import { AudioBox } from './nodes/AudioBox'; @observer @@ -197,11 +196,6 @@ export class MainView extends React.Component { var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id); if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) { - if (!this.userDoc.linkManagerDoc) { - let linkManagerDoc = new Doc(); - linkManagerDoc.allLinks = new List([]); - this.userDoc.linkManagerDoc = linkManagerDoc; - } Doc.AddDocToList(workspaces, "data", mainDoc); mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`; } @@ -504,7 +498,7 @@ export class MainView extends React.Component { } render() { - return (
+ return (
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a6ee9c2c6..238660de3 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types'; import { DragLinkAsDocument } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; -import { MainView } from '../MainView'; import { LinkFollowBox } from './LinkFollowBox'; import './LinkMenu.scss'; import React = require("react"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index abf26826c..a0f8523a2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -842,6 +842,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F this._editorView && this._editorView.destroy(); } onPointerDown = (e: React.PointerEvent): void => { + FormattedTextBoxComment.textBox = this; let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { @@ -1018,11 +1019,13 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } render() { + trace(); let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground - ? "none" : "all"; + let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; if (this.props.isSelected()) { FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); + } else if (FormattedTextBoxComment.textBox === this) { + FormattedTextBoxComment.Hide(); } return (
{ let keep = e.target && (e.target as any).type === "checkbox" ? true : false; @@ -91,62 +93,67 @@ export class FormattedTextBoxComment { let state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && - lastState.selection.eq(state.selection)) return; + lastState.selection.eq(state.selection)) { + return; + } - if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return; + const textBox = FormattedTextBoxComment.textBox; + if (!textBox || !textBox.props) { + return; + } let set = "none"; - if (FormattedTextBoxComment.textBox && state.selection.$from) { - let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + let nbef = 0; + // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date + if (state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const spos = state.selection.$from.pos - nbef; - const epos = state.selection.$from.pos + naft; - let child = state.selection.$from.nodeBefore; - let mark = child && findOtherUserMark(child.marks); let noselection = view.state.selection.$from === view.state.selection.$to; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + let mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark); + FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } - if (mark && child && nbef && naft) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("main-div") as any).getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + if (mark && child && ((nbef && naft) || !noselection)) { + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); set = ""; } } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. if (set === "none" && state.selection.$from) { - FormattedTextBoxComment.textBox = undefined; - let nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + nbef = findStartOfMark(state.selection.$from, view, findLinkMark); let naft = findEndOfMark(state.selection.$from, view, findLinkMark); - let child = state.selection.$from.nodeBefore; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { - FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href); + FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - docTarget && DocServer.GetRefField(docTarget).then(linkDoc => - (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title))); + docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { + if (linkDoc instanceof Doc) { + let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); + let ext = (target && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + let text = ext && StrCast(ext.text); + ext && (FormattedTextBoxComment.tooltipText.textContent = "=> " + (text || StrCast(ext.title))); + } + }); } - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("main-div") as any).getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; set = ""; } } + if (set !== "none") { + // These are in screen coordinates + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + let box = (document.getElementById("mainView-container") as any).getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + let left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + } FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 963205206..2d92c9581 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -48,6 +48,7 @@ } .pdfViewer-text { .textLayer { + will-change: transform; span { user-select: none; } @@ -59,6 +60,7 @@ pointer-events: all; .pdfViewer-text { .textLayer { + will-change: transform; span { user-select: text; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3baa6eb09..8e0515f8a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable, runInAction, reaction, IReactionDisposer, trace } from 'mobx'; +import { action, observable, runInAction, reaction, IReactionDisposer, trace, untracked, computed } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -32,7 +32,7 @@ export class PDFBox extends DocAnnotatableComponent private _valueValue: string = ""; private _scriptValue: string = ""; private _searchString: string = ""; - private _initialScale: number | undefined; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. + private _initialScale: number = 0; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); @@ -46,6 +46,11 @@ export class PDFBox extends DocAnnotatableComponent @observable private _pdf: Opt; @observable private _pageControls = false; + constructor(props: any) { + super(props); + this._initialScale = this.props.ScreenToLocalTransform().Scale; + } + componentWillUnmount() { this._selectReactionDisposer && this._selectReactionDisposer(); } @@ -190,39 +195,46 @@ export class PDFBox extends DocAnnotatableComponent ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - render() { + @computed get renderTitleBox() { + let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); + return
+
+ {` ${this.props.Document.title}`} +
+
; + } + + @computed get renderPdfView() { trace(); const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); - let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; - if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale; + return
{ + let hit = document.elementFromPoint(e.clientX, e.clientY); + if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation + e.button === 0 && e.stopPropagation(); + } + }}> + + {this.settingsPanel()} +
; + } + + render() { + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null); if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; - return (!this.extensionDoc || noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ? -
-
- {` ${this.props.Document.title}`} -
-
: -
{ - let hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation - e.button === 0 && e.stopPropagation(); - } - }}> - - {this.settingsPanel()} -
); + return !pdfUrl || !this._pdf || !this.extensionDoc || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ? + this.renderTitleBox : this.renderPdfView; } } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 2d8f47666..936af9ab8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -52,11 +52,7 @@ class RegionAnnotation extends React.Component { this._brushDisposer = reaction( () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!), - (brushed) => { - if (brushed !== undefined) { - runInAction(() => this._brushed = brushed !== 0); - } - } + brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0) ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 060ba8613..0cb671156 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -199,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent this.extensionDoc && DocListCast(this.extensionDoc.annotations), - annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), + annotations => annotations && annotations.length && (this._annotations = annotations), { fireImmediately: true }); this._filterReactionDisposer = reaction( @@ -297,18 +297,6 @@ export class PDFViewer extends DocAnnotatableComponent { - if (removeOldAnnotations) { - this._annotations = annotations; - } - else { - this._annotations.push(...annotations); - this._annotations = new Array(...this._annotations); - } - } - @action prevAnnotation = () => { this.Index = Math.max(this.Index - 1, 0); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6aad4a6be..3bf1129b5 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -669,8 +669,8 @@ export namespace Doc { return doc; } - export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } export function linkFollowUnhighlight() { Doc.UnhighlightAll(); document.removeEventListener("pointerdown", linkFollowUnhighlight); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 56ea5bfe1..833e44bf6 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -15,7 +15,6 @@ import { RouteStore } from "../../RouteStore"; import { InkingControl } from "../../../client/views/InkingControl"; import { DragManager } from "../../../client/util/DragManager"; import { nullAudio } from "../../../new_fields/URLField"; -import { LinkManager } from "../../../client/util/LinkManager"; export class CurrentUserUtils { private static curr_id: string; -- cgit v1.2.3-70-g09d2 From 76411ba009c4265751431d255ab3cc9a3cfab69f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 12 Nov 2019 21:28:39 -0500 Subject: switched link preview to be a document view --- src/client/util/RichTextSchema.tsx | 1 - .../views/nodes/ContentFittingDocumentView.tsx | 2 + src/client/views/nodes/DocumentView.tsx | 6 ++- src/client/views/nodes/FormattedTextBoxComment.tsx | 62 +++++++++++++++++----- 4 files changed, 54 insertions(+), 17 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 76b8aeaa1..1004cb3d4 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -18,7 +18,6 @@ import { Transform } from "./Transform"; import React = require("react"); import { BoolCast, NumCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index f41c4fc91..751355403 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -37,6 +37,7 @@ interface ContentFittingDocumentViewProps { whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; + dontRegisterView?: boolean; setPreviewScript: (script: string) => void; previewScript?: string; } @@ -108,6 +109,7 @@ export class ContentFittingDocumentView extends React.Component diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 93052ea73..4fbb0516f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -74,6 +74,7 @@ export interface DocumentViewProps { getScale: () => number; animateBetweenIcon?: (maximize: boolean, target: number[]) => void; ChromeHeight?: () => number; + dontRegisterView?: boolean; layoutKey?: string; } @@ -98,7 +99,8 @@ export class DocumentView extends DocComponent(Docu @action componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); - DocumentManager.Instance.DocumentViews.push(this); + + !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @action @@ -111,7 +113,7 @@ export class DocumentView extends DocComponent(Docu componentWillUnmount() { this._dropDisposer && this._dropDisposer(); Doc.UnBrushDoc(this.props.Document); - DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index f35ca502f..98cd17972 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -2,16 +2,19 @@ import { Plugin, EditorState } from "prosemirror-state"; import './FormattedTextBoxComment.scss'; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym } from "../../../new_fields/Doc"; import { schema } from "../../util/RichTextSchema"; import { DocServer } from "../../DocServer"; -import { Utils } from "../../../Utils"; -import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; +import { Utils, returnTrue, returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; +import { StrCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { FormattedTextBox } from "./FormattedTextBox"; -import { DocUtils } from "../../documents/Documents"; -import { isBuffer } from "util"; import { DocumentManager } from "../../util/DocumentManager"; import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentView } from "./DocumentView"; +import React = require("react"); +import * as ReactDOM from 'react-dom'; +import { Transform } from "../../util/Transform"; +import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -67,22 +70,28 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; + FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; + FormattedTextBoxComment.tooltip.style.width = "100%"; + FormattedTextBoxComment.tooltip.style.height = "100%"; + FormattedTextBoxComment.tooltip.style.overflow = "hidden"; + FormattedTextBoxComment.tooltip.style.display = "none"; FormattedTextBoxComment.tooltip.appendChild(input); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; - if (FormattedTextBoxComment.linkDoc && !keep && FormattedTextBoxComment.textBox) { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, FormattedTextBoxComment.textBox.props.Document, - (doc: Doc, maxLocation: string) => FormattedTextBoxComment.textBox!.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + const textBox = FormattedTextBoxComment.textBox; + if (FormattedTextBoxComment.linkDoc && !keep && textBox) { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; - FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation( + textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, FormattedTextBoxComment.opened, keep); e.stopPropagation(); }; root && root.appendChild(FormattedTextBoxComment.tooltip); } - this.update(view, undefined); + //this.update(view, undefined); } public static Hide() { @@ -144,9 +153,34 @@ export class FormattedTextBoxComment { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); - let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... - let text = ext && StrCast(ext.text); - ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { + + } + target && ReactDOM.render( 350} + PanelHeight={() => 250} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + // let text = ext && StrCast(ext.text); + // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); } }); } @@ -168,5 +202,5 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } - destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; } + destroy() { }//FormattedTextBoxComment.tooltip.style.display = "none"; } } -- cgit v1.2.3-70-g09d2 From a93ee99c9dd3b5ecc5160f98f9dffeb119b4dcce Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 13:46:23 -0500 Subject: fixed recentlyClosed on startup and added clearAll. fixed textbox comment layout a bit. --- src/client/util/SearchUtil.ts | 7 ++- .../views/collections/CollectionStackingView.tsx | 1 - .../views/collections/CollectionTreeView.tsx | 13 ++-- .../views/nodes/ContentFittingDocumentView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 5 +- .../views/nodes/FormattedTextBoxComment.scss | 1 - src/client/views/nodes/FormattedTextBoxComment.tsx | 71 +++++++++++----------- .../views/presentationview/PresElementBox.tsx | 1 - .../authentication/models/current_user_utils.ts | 2 + 9 files changed, 56 insertions(+), 46 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 6706dcb89..2cf13680a 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -28,6 +28,7 @@ export namespace SearchUtil { start?: number; rows?: number; fq?: string; + allowAliases?: boolean; } export function Search(query: string, returnDocs: true, options?: SearchParams): Promise; export function Search(query: string, returnDocs: false, options?: SearchParams): Promise; @@ -73,7 +74,7 @@ export namespace SearchUtil { const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); for (let i = 0; i < ids.length; i++) { let testDoc = docs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { theDocs.push(testDoc); theLines.push([]); } @@ -88,9 +89,9 @@ export namespace SearchUtil { const proto = Doc.GetProto(doc); const protoId = proto[Id]; if (returnDocs) { - return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).docs; + return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).docs; } else { - return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).ids; + return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).ids; } // return Search(`{!join from=id to=proto_i}id:${protoId}`, true); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 15033e51a..be3bfca0a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -168,7 +168,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return { />) onWorkspaceContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking && this.props.document !== CurrentUserUtils.UserDocument.workspaces) { + if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view + if (this.props.document === CurrentUserUtils.UserDocument.recentlyClosed) { + ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.GetProto(CurrentUserUtils.UserDocument.recentlyClosed as Doc).data = new List(), icon: "plus" }); + } else if (this.props.document !== CurrentUserUtils.UserDocument.workspaces) { ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" }); ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "inTab"), icon: "folder" }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight"), icon: "caret-square-right" }); @@ -317,7 +318,6 @@ class TreeView extends React.Component { CurrentUserUtils.UserDocument.recentlyClosed = new List(), icon: "plus" }); + e.stopPropagation(); + e.preventDefault(); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } else { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" }); diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 751355403..c8255b6fe 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -20,7 +20,6 @@ interface ContentFittingDocumentViewProps { childDocs?: Doc[]; renderDepth: number; fitToBox?: boolean; - fieldKey: string; PanelWidth: () => number; PanelHeight: () => number; ruleProvider: Doc | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 65a51b357..8b1e65663 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -843,7 +843,10 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } onPointerUp = (e: React.PointerEvent): void => { - if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; } + if (!(e.nativeEvent as any).formattedHandled) { + FormattedTextBoxComment.textBox = this; + FormattedTextBoxComment.update(this._editorView!); + } (e.nativeEvent as any).formattedHandled = true; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss index 792cee182..2dd63ec21 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/FormattedTextBoxComment.scss @@ -5,7 +5,6 @@ background: white; border: 1px solid silver; border-radius: 2px; - padding: 2px 10px; margin-bottom: 7px; -webkit-transform: translateX(-50%); transform: translateX(-50%); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 98cd17972..29b4b6383 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -1,20 +1,18 @@ -import { Plugin, EditorState } from "prosemirror-state"; -import './FormattedTextBoxComment.scss'; -import { ResolvedPos, Mark } from "prosemirror-model"; +import { Mark, ResolvedPos } from "prosemirror-model"; +import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, WidthSym } from "../../../new_fields/Doc"; -import { schema } from "../../util/RichTextSchema"; +import * as ReactDOM from 'react-dom'; +import { Doc } from "../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; -import { Utils, returnTrue, returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; -import { StrCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { FormattedTextBox } from "./FormattedTextBox"; import { DocumentManager } from "../../util/DocumentManager"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentView } from "./DocumentView"; -import React = require("react"); -import * as ReactDOM from 'react-dom'; +import { schema } from "../../util/RichTextSchema"; import { Transform } from "../../util/Transform"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import './FormattedTextBoxComment.scss'; +import React = require("react"); export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -107,7 +105,7 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); } - update(view: EditorView, lastState?: EditorState) { + static update(view: EditorView, lastState?: EditorState) { let state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && @@ -122,6 +120,8 @@ export class FormattedTextBoxComment { } let set = "none"; let nbef = 0; + FormattedTextBoxComment.tooltip.style.width = ""; + FormattedTextBoxComment.tooltip.style.height = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -152,32 +152,35 @@ export class FormattedTextBoxComment { docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; - let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); + const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); } catch (e) { } - target && ReactDOM.render( 350} - PanelHeight={() => 250} - focus={emptyFunction} - whenActiveChanged={returnFalse} - />, FormattedTextBoxComment.tooltipText); + if (target) { + ReactDOM.render( Math.min(350, NumCast(target.width, 350))} + PanelHeight={() => Math.min(250, NumCast(target.height, 250))} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; + FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; + } // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... // let text = ext && StrCast(ext.text); // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 17b2094ec..f50a3a0ef 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -172,7 +172,6 @@ export class PresElementBox extends DocComponent(P recent && PromiseValue(recent.data).then(DocListCast)); // this is equivalent to using PrefetchProxies to make sure all the sidebarButtons and noteType internal Doc's have been retrieved. PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast)); PromiseValue(Cast(doc.sidebarButtons, Doc)).then(stackingDoc => { -- cgit v1.2.3-70-g09d2 From e87b4b99323875afce2d9847f3bddd4196b85b81 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 14:48:57 -0500 Subject: added a lockedTransform field to lock pan/zoom. fixed text scrollintoview to scroll only when necessary. --- src/client/documents/Documents.ts | 3 ++- src/client/util/RichTextSchema.tsx | 1 + .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 7 +++++++ src/client/views/nodes/FormattedTextBox.tsx | 4 +++- src/new_fields/documentSchemas.ts | 3 ++- 6 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src/client/util') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ba9f87025..1a9d67d83 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -76,7 +76,8 @@ export interface DocumentOptions { viewType?: number; backgroundColor?: string; ignoreClick?: boolean; - lockedPosition?: boolean; + lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged + lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed opacity?: number; defaultBackgroundColor?: string; dropAction?: dropActionType; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1004cb3d4..0d1ae3841 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -679,6 +679,7 @@ export class DashDocView { bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} + dontRegisterView={true} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} ContentScaling={this.contentScaling} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6e0f75bc1..0c5f4ec80 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -333,7 +333,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return; + if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -355,7 +355,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number, panType: string = "None") { - if (!this.Document.lockedPosition || this.Document.inOverlay) { + if (!this.Document.lockedTransform || this.Document.inOverlay) { this.Document.panTransformType = panType; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7e81cd673..98c610c68 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -372,6 +372,12 @@ export class DocumentView extends DocComponent(Docu this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; } + @undoBatch + @action + toggleLockTransform = (): void => { + this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true; + } + listen = async () => { Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, @@ -444,6 +450,7 @@ export class DocumentView extends DocComponent(Docu layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); + layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8b1e65663..015a21fd2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -774,7 +774,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; let r1 = refNode && refNode.getBoundingClientRect(); let r3 = self._ref.current!.getBoundingClientRect(); - r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + if (r1.top < r3.top || r1.top > r3.bottom) { + r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + } return true; }, dispatchTransaction: this.dispatchTransaction, diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e2730914f..fa47374f1 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -31,7 +31,8 @@ export const documentSchema = createSchema({ summarizedDocs: listSpec(Doc), // documents that are summarized by this document (and which will typically be opened by clicking this document) maximizedDocs: listSpec(Doc), // documents to maximize when clicking this document (generally this document will be an icon) maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) - lockedPosition: "boolean", // whether the document can be spatially manipulated + lockedPosition: "boolean", // whether the document can be moved (dragged) + lockedTransform: "boolean", // whether the document can be panned/zoomed inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently borderRounding: "string", // border radius rounding of document searchFields: "string", // the search fields to display when this document matches a search in its metadata -- cgit v1.2.3-70-g09d2 From c6b602e2ab00d3a38e56164ecc928d5db8c12c72 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 14 Nov 2019 11:35:33 -0500 Subject: fixed warning/errors. cleaned up link following box a bit. --- src/client/util/RichTextSchema.tsx | 4 ++-- src/client/util/TooltipTextMenu.tsx | 20 ++++++++----------- .../views/collections/CollectionSchemaView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 1 + src/client/views/nodes/FormattedTextBoxComment.tsx | 23 ++++++++++++++++------ src/client/views/search/FilterBox.tsx | 4 ++-- 8 files changed, 32 insertions(+), 25 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0d1ae3841..0fc526ca7 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -575,8 +575,8 @@ export class ImageResizeView { this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); - let wid = Number(getComputedStyle(self._img).width!.replace(/px/, "")); - let hgt = Number(getComputedStyle(self._img).height!.replace(/px/, "")); + let wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); + let hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); const startX = e.pageX; const startWidth = parseFloat(node.attrs.width); const onpointermove = (e: any) => { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 38471a955..5e11ae653 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1000,12 +1000,10 @@ export class TooltipTextMenu { let activeMarks: MarkType[]; if (!empty) { activeMarks = markGroup.filter(mark => { - if (dispatch) { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - let { $from, $to } = ranges[i]; - return state.doc.rangeHasMark($from.pos, $to.pos, mark); - } + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + return state.doc.rangeHasMark($from.pos, $to.pos, mark); } return false; }); @@ -1024,13 +1022,11 @@ export class TooltipTextMenu { } this._activeMarks = ref_node.marks; activeMarks = markGroup.filter(mark_type => { - if (dispatch) { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } - let mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); } + let mark = state.schema.mark(mark_type); + return ref_node.marks.includes(mark); return false; }); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 203c68463..ebd47fd19 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -141,7 +141,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { (Docu {searchHighlight}
} - + ; } render() { if (!this.props.Document) return (null); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index ae9273639..83ecc4657 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -25,7 +25,7 @@ export class FontIconBox extends DocComponent( this._backgroundReaction = reaction(() => this.props.Document.backgroundColor, () => { if (this._ref && this._ref.current) { - let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!); + let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor); let colsum = (col.r + col.g + col.b); if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black"); else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 015a21fd2..6d60e8f77 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -288,6 +288,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F if (context === node) return { from: offset, to: offset + node.nodeSize }; if (node.isBlock) { + // tslint:disable-next-line: prefer-for-of for (let i = 0; i < (context.content as any).content.length; i++) { let result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 29b4b6383..32efb16cf 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -13,6 +13,7 @@ import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; import React = require("react"); +import { Docs } from "../../documents/Documents"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -51,6 +52,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; + static tooltipInput: HTMLInputElement; static start: number; static end: number; static mark: Mark; @@ -60,10 +62,15 @@ export class FormattedTextBoxComment { constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { const root = document.getElementById("root"); - let input = document.createElement("input"); - input.type = "checkbox"; + FormattedTextBoxComment.tooltipInput = document.createElement("input"); + FormattedTextBoxComment.tooltipInput.type = "checkbox"; FormattedTextBoxComment.tooltip = document.createElement("div"); FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; + FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; + FormattedTextBoxComment.tooltipText.style.width = "100%"; + FormattedTextBoxComment.tooltipText.style.height = "100%"; + FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; @@ -73,13 +80,15 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.height = "100%"; FormattedTextBoxComment.tooltip.style.overflow = "hidden"; FormattedTextBoxComment.tooltip.style.display = "none"; - FormattedTextBoxComment.tooltip.appendChild(input); + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { width: 200, height: 400 }), undefined, "onRight"); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( @@ -120,8 +129,10 @@ export class FormattedTextBoxComment { } let set = "none"; let nbef = 0; + FormattedTextBoxComment.tooltipInput.style.display = "none"; FormattedTextBoxComment.tooltip.style.width = ""; FormattedTextBoxComment.tooltip.style.height = ""; + (FormattedTextBoxComment.tooltipText as any).href = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -136,6 +147,7 @@ export class FormattedTextBoxComment { if (mark && child && ((nbef && naft) || !noselection)) { FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); set = ""; + FormattedTextBoxComment.tooltipInput.style.display = ""; } } // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. @@ -147,6 +159,7 @@ export class FormattedTextBoxComment { let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { @@ -155,9 +168,7 @@ export class FormattedTextBoxComment { const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { - - } + } catch (e) { } if (target) { ReactDOM.render( { @@ -114,7 +114,7 @@ export class FilterBox extends React.Component { acc[i].classList.toggle("active"); var panel = acc[i].nextElementSibling as HTMLElement; panel.style.overflow = "hidden"; - panel.style.maxHeight = null; + panel.style.maxHeight = ""; } } }); -- cgit v1.2.3-70-g09d2 From e3f06e390f98cc5b97d63fc287daff994d5fef6f Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 14 Nov 2019 23:01:49 -0500 Subject: double cliking dragger collapses menu --- src/client/util/RichTextSchema.tsx | 1 + src/client/util/TooltipTextMenu.scss | 399 ++--------------------- src/client/util/TooltipTextMenu.tsx | 610 +++++++++++++++-------------------- 3 files changed, 278 insertions(+), 732 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1eea529d2..69f9a4c53 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -130,6 +130,7 @@ export const nodes: { [index: string]: NodeSpec } = { // } // }] }, + // :: NodeSpec An inline image (``) node. Supports `src`, // `alt`, and `href` attributes. The latter two default to the empty // string. diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 1f619b4f5..8310a0da6 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,6 +1,5 @@ @import "../views/globalCssVariables"; .ProseMirror-menu-dropdown-wrap { - // margin: 0 4px; display: inline-block; position: relative; } @@ -39,9 +38,10 @@ font-size: 12px; background: white; border: 1px solid rgb(223, 223, 223); - min-width: 50px; + min-width: 40px; z-index: 50000; position: absolute; + box-sizing: content-box; .ProseMirror-menu-dropdown-item { cursor: pointer; @@ -91,20 +91,22 @@ .wrapper { position: absolute; pointer-events: all; -} + display: flex; + align-items: center; + transform: translateY(-85px); -.tooltipMenu { - position: absolute; - z-index: 20000; + height: 35px; background: #323232; border-radius: 6px; - transform: translateY(-85px); + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + +} + +.tooltipMenu, .basic-tools { + z-index: 20000; pointer-events: all; - height: 35px; - // width: 650px; padding: 3px; padding-bottom: 5px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); display: flex; align-items: center; @@ -203,7 +205,7 @@ } .underline svg { - margin-top: 15px; + margin-top: 13px; } .font-size-indicator { @@ -232,21 +234,19 @@ width: 1em; height: 1em; stroke-width: 3; - // stroke: greenyellow; fill: greenyellow; margin-right: 15px; } -.dragger { +.dragger-wrapper { color: #eee; - margin-left: 5px; - width: 16px; height: 22px; - display: inline-block; + padding: 0 5px; + box-sizing: content-box; cursor: grab; - .dragger-wrapper { - width: 100%; + .dragger { + width: 18px; height: 100%; display: flex; justify-content: space-evenly; @@ -259,11 +259,6 @@ } } -// .menuicon:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown, -// .menuicon-active:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown { -// background-color: black; -// } - .button-dropdown-wrapper { display: flex; align-content: center; @@ -374,360 +369,4 @@ button.colorPicker { &.active { border: 2px solid white !important; } -} - -// @import "../views/globalCssVariables"; - -// .ProseMirror-textblock-dropdown { -// min-width: 3em; -// } - -// .ProseMirror-menu { -// margin: 0 -4px; -// line-height: 1; -// } - -// .ProseMirror-tooltip .ProseMirror-menu { -// width: -webkit-fit-content; -// width: fit-content; -// white-space: pre; -// } - -// .ProseMirror-menuitem { -// margin-right: 3px; -// display: inline-block; -// z-index: 50000; -// position: relative; -// } - -// .ProseMirror-menuseparator { -// // border-right: 1px solid #ddd; -// margin-right: 3px; -// } - -// .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { -// font-size: 90%; -// white-space: nowrap; -// } - -// .ProseMirror-menu-dropdown { -// vertical-align: 1px; -// cursor: pointer; -// position: relative; -// padding-right: 15px; -// margin: 3px; -// background: white; -// border-radius: 3px; -// text-align: center; -// } - -// .ProseMirror-menu-dropdown-wrap { -// padding: 1px 0 1px 4px; -// display: inline-block; -// position: relative; -// } - -// .ProseMirror-menu-dropdown:after { -// content: ""; -// border-left: 4px solid transparent; -// border-right: 4px solid transparent; -// border-top: 4px solid currentColor; -// opacity: .6; -// position: absolute; -// right: 4px; -// top: calc(50% - 2px); -// } - -// .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { -// background: $dark-color; -// color:white; -// border: 1px solid rgb(223, 223, 223); -// padding: 2px; -// } - -// .ProseMirror-menu-dropdown-menu { -// z-index: 50000; -// min-width: 6em; -// background: white; -// position: absolute; -// } - -// .linking { -// text-align: center; -// } - -// .ProseMirror-menu-dropdown-item { -// cursor: pointer; -// padding: 2px 8px 2px 4px; -// width: auto; -// z-index: 100000; -// } - -// .ProseMirror-menu-dropdown-item:hover { -// background: white; -// } - -// .ProseMirror-menu-submenu-wrap { -// position: relative; -// margin-right: -4px; -// } - -// .ProseMirror-menu-submenu-label:after { -// content: ""; -// border-top: 4px solid transparent; -// border-bottom: 4px solid transparent; -// border-left: 4px solid currentColor; -// opacity: .6; -// position: absolute; -// right: 4px; -// top: calc(50% - 4px); -// } - -// .ProseMirror-menu-submenu { -// display: none; -// min-width: 4em; -// left: 100%; -// top: -3px; -// } - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-disabled { -// opacity: .3; -// } - -// .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { -// display: block; -// } - -// .ProseMirror-menubar { -// border-top-left-radius: inherit; -// border-top-right-radius: inherit; -// position: relative; -// min-height: 1em; -// color: white; -// padding: 10px 10px; -// top: 0; left: 0; right: 0; -// border-bottom: 1px solid silver; -// background:$dark-color; -// z-index: 10; -// -moz-box-sizing: border-box; -// box-sizing: border-box; -// overflow: visible; -// } - -// .ProseMirror-icon { -// display: inline-block; -// line-height: .8; -// vertical-align: -2px; /* Compensate for padding */ -// padding: 2px 8px; -// cursor: pointer; -// } - -// .ProseMirror-menu-disabled.ProseMirror-icon { -// cursor: default; -// } - -// .ProseMirror-icon svg { -// fill:white; -// height: 1em; -// } - -// .ProseMirror-icon span { -// vertical-align: text-top; -// } - -// .ProseMirror ul, .ProseMirror ol { -// padding-left: 30px; -// } - -// .ProseMirror blockquote { -// padding-left: 1em; -// border-left: 3px solid #eee; -// margin-left: 0; margin-right: 0; -// } - -// .ProseMirror-example-setup-style img { -// cursor: default; -// } - -// .ProseMirror-prompt { -// background: white; -// padding: 5px 10px 5px 15px; -// border: 1px solid silver; -// position: fixed; -// border-radius: 3px; -// z-index: 11; -// box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); -// } - -// .ProseMirror-prompt h5 { -// margin: 0; -// font-weight: normal; -// font-size: 100%; -// color: #444; -// } - -// .ProseMirror-prompt input[type="text"], -// .ProseMirror-prompt textarea { -// background: white; -// border: none; -// outline: none; -// } - -// .ProseMirror-prompt input[type="text"] { -// padding: 0 4px; -// } - -// .ProseMirror-prompt-close { -// position: absolute; -// left: 2px; top: 1px; -// color: #666; -// border: none; background: transparent; padding: 0; -// } - -// .ProseMirror-prompt-close:after { -// content: "✕"; -// font-size: 12px; -// } - -// .ProseMirror-invalid { -// background: #ffc; -// border: 1px solid #cc7; -// border-radius: 4px; -// padding: 5px 10px; -// position: absolute; -// min-width: 10em; -// } - -// .ProseMirror-prompt-buttons { -// margin-top: 5px; -// display: none; -// } - -// .tooltipMenu { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-85px); -// pointer-events: all; -// height: fit-content; -// width:550px; -// .ProseMirror-example-setup-style hr { -// padding: 2px 10px; -// border: none; -// margin: 1em 0; -// } - -// .ProseMirror-example-setup-style hr:after { -// content: ""; -// display: block; -// height: 1px; -// background-color: silver; -// line-height: 2px; -// } -// } - -// .tooltipExtras { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-115px); -// pointer-events: all; -// height: 25px; -// width:fit-content; -// .ProseMirror-example-setup-style hr { -// padding: 2px 10px; -// border: none; -// margin: 1em 0; -// } - -// .ProseMirror-example-setup-style hr:after { -// content: ""; -// display: block; -// height: 1px; -// background-color: silver; -// line-height: 2px; -// } -// } - -// .wrapper { -// position: absolute; -// pointer-events: all; -// } - -// .menuicon { -// display: inline-block; -// border-right: 1px solid white(0, 0, 0, 0.2); -// //color: rgb(19, 18, 18); -// color: rgb(226, 21, 21); -// line-height: 1; -// padding: 0px 2px; -// margin: 1px; -// cursor: pointer; -// text-align: center; -// min-width: 10px; - -// } -// .strong, .heading { font-weight: bold; } -// .em { font-style: italic; } -// .underline {text-decoration: underline} -// .superscript {vertical-align:super} -// .subscript { vertical-align:sub } -// .strikethrough {text-decoration-line:line-through} -// .font-size-indicator { -// font-size: 12px; -// padding-right: 0px; -// } -// .summarize{ -// color: white; -// height: 20px; -// text-align: center; -// } - -// .brush{ -// display: inline-block; -// width: 1em; -// height: 1em; -// stroke-width: 0; -// stroke: currentColor; -// fill: currentColor; -// margin-right: 15px; -// } - -// .brush-active{ -// display: inline-block; -// width: 1em; -// height: 1em; -// stroke-width: 3; -// stroke: greenyellow; -// fill: greenyellow; -// margin-right: 15px; -// } - -// .dragger{ -// color: #eee; -// margin-left: 5px; -// } \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 564d9f0df..6001f9840 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -30,9 +30,8 @@ export class TooltipTextMenu { public static Toolbar: HTMLDivElement | undefined; - // editor state + // editor state properties private view: EditorView; - // private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[] = []; private fontSizes: MarkType[] = []; private listTypes: (NodeType | any)[] = []; @@ -42,13 +41,16 @@ export class TooltipTextMenu { private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); private _collapsed: boolean = false; - //private link: HTMLAnchorElement; // editor doms public tooltip: HTMLElement = document.createElement("div"); private wrapper: HTMLDivElement = document.createElement("div"); // editor button doms + private colorDom?: Node; + private colorDropdownDom?: Node; + private highlightDom?: Node; + private highlightDropdownDom?: Node; private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; @@ -58,42 +60,32 @@ export class TooltipTextMenu { private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private colorDom?: Node; - private colorDropdownDom?: Node; - private highlightDom?: Node; - private highlightDropdownDom?: Node; - - - // private _collapseBtn?: MenuItem; - // private _brushIsEmpty: boolean = true; - + private basicTools?: HTMLElement; constructor(view: EditorView) { this.view = view; - // // replace old active menu with this - // if (TooltipTextMenuManager.Instance.activeMenu) { - // TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); - // } - // TooltipTextMenuManager.Instance.activeMenu = this; - - // initialize the tooltip - this.createTooltip(view); + // initialize the tooltip -- sets this.tooltip + this.initTooltip(view); // initialize the wrapper this.wrapper = document.createElement("div"); this.wrapper.className = "wrapper"; this.wrapper.appendChild(this.tooltip); - // positioning? + // initialize the dragger -- appends it to the wrapper + this.createDragger(); + TooltipTextMenu.Toolbar = this.wrapper; } - private async createTooltip(view: EditorView) { + private async initTooltip(view: EditorView) { // initialize tooltip dom this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; + this.basicTools = document.createElement("div"); + this.basicTools.className = "basic-tools"; // init buttons to the tooltip -- paths to svgs are obtained from fontawesome let items = [ @@ -113,12 +105,15 @@ export class TooltipTextMenu { switch (dom.title) { case "Bold": this._marksToDoms.set(schema.mark(schema.marks.strong), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; case "Italic": this._marksToDoms.set(schema.mark(schema.marks.em), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; case "Underline": this._marksToDoms.set(schema.mark(schema.marks.underline), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; } @@ -172,19 +167,12 @@ export class TooltipTextMenu { this.tooltip.appendChild(this._brushDropdownDom); // star - // this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); - // + // list types dropdown this.updateListItemDropdown(":", this.listTypeBtnDom); - // await this.updateFromDash(view, undefined, undefined); - // TooltipTextMenu.Toolbar = this.wrapper; - - // dragger - // TODO: onclick handler in drag handles collapsing - this.createDragger(); } initFontStyles() { @@ -224,13 +212,15 @@ export class TooltipTextMenu { this.listTypes = Array.from(this.listTypeToIcon.keys()); } + // creates dragger element that allows dragging and collapsing (on double click) + // of editor and appends it to the wrapper createDragger() { - const dragger = document.createElement("div"); - dragger.className = "dragger"; - let draggerWrapper = document.createElement("div"); draggerWrapper.className = "dragger-wrapper"; + let dragger = document.createElement("div"); + dragger.className = "dragger"; + let line1 = document.createElement("span"); line1.className = "dragger-line"; let line2 = document.createElement("span"); @@ -238,14 +228,72 @@ export class TooltipTextMenu { let line3 = document.createElement("span"); line3.className = "dragger-line"; - draggerWrapper.appendChild(line1); - draggerWrapper.appendChild(line2); - draggerWrapper.appendChild(line3); + dragger.appendChild(line1); + dragger.appendChild(line2); + dragger.appendChild(line3); - dragger.appendChild(draggerWrapper); + draggerWrapper.appendChild(dragger); - this.tooltip.appendChild(dragger); - this.dragElement(dragger); + this.wrapper.appendChild(draggerWrapper); + this.dragElement(draggerWrapper); + } + + dragElement(elmnt: HTMLElement) { + var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + if (elmnt) { + // if present, the header is where you move the DIV from: + elmnt.onpointerdown = dragMouseDown; + elmnt.ondblclick = onClick; + } + const self = this; + + function dragMouseDown(e: PointerEvent) { + e = e || window.event; + //e.preventDefault(); + // get the mouse cursor position at startup: + pos3 = e.clientX; + pos4 = e.clientY; + document.onpointerup = closeDragElement; + // call a function whenever the cursor moves: + document.onpointermove = elementDrag; + } + + function onClick(e: MouseEvent) { + self._collapsed = !self._collapsed; + const children = self.wrapper.childNodes; + if (self._collapsed && children.length > 0) { + self.wrapper.removeChild(self.tooltip); + self.basicTools && self.wrapper.prepend(self.basicTools); + } + else { + self.wrapper.prepend(self.tooltip); + self.basicTools && self.wrapper.removeChild(self.basicTools); + } + } + + function elementDrag(e: PointerEvent) { + e = e || window.event; + //e.preventDefault(); + // calculate the new cursor position: + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + // set the element's new position: + // elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; + // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; + + self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px"; + self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px"; + } + + function closeDragElement() { + // stop moving when mouse button is released: + document.onpointerup = null; + document.onpointermove = null; + //self.highlightSearchTerms(self.state, ["hello"]); + //FormattedTextBox.Instance.unhighlightSearchTerms(); + } } //label of dropdown will change to given label @@ -261,7 +309,7 @@ export class TooltipTextMenu { let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { label: label, - css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" + css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom; if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); } else { @@ -270,8 +318,6 @@ export class TooltipTextMenu { this.fontSizeDom = newfontSizeDom; } - // Make the DIV element draggable - //label of dropdown will change to given label updateFontStyleDropdown(label: string) { //filtering function - might be unecessary @@ -285,7 +331,7 @@ export class TooltipTextMenu { let newfontStyleDom = (new Dropdown(cut(fontBtns), { label: label, - css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" + css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom; if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); } else { @@ -299,11 +345,7 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - // this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); - // this.linkText.style.cssFloat = "left"; - // this.linkText.style.marginRight = "5px"; - // this.linkText.style.marginLeft = "5px"; this.linkText.setAttribute("contenteditable", "true"); this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; @@ -391,13 +433,11 @@ export class TooltipTextMenu { e.preventDefault(); } }; - // this.tooltip.appendChild(this.linkEditor); } async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - // let href = link!.attrs.href; if (link) { let href = link.attrs.href; if (href) { @@ -445,7 +485,6 @@ export class TooltipTextMenu { input.type = "text"; input.placeholder = "Enter URL"; - console.log(targetTitle); if (targetTitle) input.value = targetTitle; input.onclick = (e: MouseEvent) => { input.select(); @@ -514,62 +553,6 @@ export class TooltipTextMenu { return linkDropdown; } - dragElement(elmnt: HTMLElement) { - var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; - if (elmnt) { - // if present, the header is where you move the DIV from: - elmnt.onpointerdown = dragMouseDown; - elmnt.ondblclick = onClick; - } - const self = this; - - function dragMouseDown(e: PointerEvent) { - e = e || window.event; - //e.preventDefault(); - // get the mouse cursor position at startup: - pos3 = e.clientX; - pos4 = e.clientY; - document.onpointerup = closeDragElement; - // call a function whenever the cursor moves: - document.onpointermove = elementDrag; - } - - function onClick(e: MouseEvent) { - self._collapsed = !self._collapsed; - const children = self.wrapper.childNodes; - if (self._collapsed && children.length > 1) { - self.wrapper.removeChild(self.tooltip); - } - else { - self.wrapper.appendChild(self.tooltip); - } - } - - function elementDrag(e: PointerEvent) { - e = e || window.event; - //e.preventDefault(); - // calculate the new cursor position: - pos1 = pos3 - e.clientX; - pos2 = pos4 - e.clientY; - pos3 = e.clientX; - pos4 = e.clientY; - // set the element's new position: - // elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; - // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; - - self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px"; - self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px"; - } - - function closeDragElement() { - // stop moving when mouse button is released: - document.onpointerup = null; - document.onpointermove = null; - //self.highlightSearchTerms(self.state, ["hello"]); - //FormattedTextBox.Instance.unhighlightSearchTerms(); - } - } - // makeLinkWithState = (state: EditorState, target: string, location: string) => { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } @@ -616,10 +599,70 @@ export class TooltipTextMenu { } } } + } - + deleteLinkItem() { + const icon = { + height: 16, width: 16, + path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" + }; + return new MenuItem({ + title: "Delete Link", + label: "X", + icon: icon, + css: "color: red", + class: "summarize", + execEvent: "", + run: (state, dispatch) => { + this.deleteLink(); + } + }); } + createLink() { + let markType = schema.marks.link; + return new MenuItem({ + title: "Add or remove link", + label: "Add or remove link", + execEvent: "", + icon: icons.link, + css: "color:white;", + class: "menuicon", + enable(state) { return !state.selection.empty; }, + run: (state, dispatch, view) => { + // to remove link + let curLink = ""; + if (this.markActive(state, markType)) { + + let { from, $from, to, empty } = state.selection; + let node = state.doc.nodeAt(from); + node && node.marks.map(m => { + m.type === markType && (curLink = m.attrs.href); + }); + //toggleMark(markType)(state, dispatch); + //return true; + } + // to create link + openPrompt({ + title: "Create a link", + fields: { + href: new TextField({ + value: curLink, + label: "Link Target", + required: true + }), + title: new TextField({ label: "Title" }) + }, + callback(attrs: any) { + toggleMark(markType, attrs)(view.state, view.dispatch); + view.focus(); + }, + flyout_top: 0, + flyout_left: 0 + }); + } + }); + } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { @@ -644,108 +687,6 @@ export class TooltipTextMenu { return listTypeBtn; } - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text - changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { - let { $cursor, ranges } = view.state.selection as TextSelection; - let state = view.state; - let dispatch = view.dispatch; - - //remove all other active font marks - fontMarks.forEach((type) => { - if (dispatch) { - if ($cursor) { - if (type.isInSet(state.storedMarks || $cursor.marks())) { - dispatch(state.tr.removeStoredMark(type)); - } - } else { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - let { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, type); - } - for (let i of ranges) { - if (has) { - toggleMark(type)(view.state, view.dispatch, view); - } - } - } - } - }); - - if (markType) { - // fontsize - if (markType.name[0] === 'p') { - let size = this.fontSizeToNum.get(markType); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - } - else { - let fontName = this.fontStylesToName.get(markType); - if (fontName) { this.updateFontStyleDropdown(fontName); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - } - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); - view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); - } - else toggleMark(markType)(view.state, view.dispatch, view); - } - } - - //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { - //remove oldif (nodeType) { //add new - if (nodeType === schema.nodes.bullet_list) { - wrapInList(nodeType)(view.state, view.dispatch); - } else { - var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - view.dispatch(tx2); - })) { - let tx2 = view.state.tr; - let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - view.dispatch(tx3); - } - } - } - - //makes a button for the drop down FOR MARKS - //css is the style you want applied to the button - dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { - return new MenuItem({ - title: "", - label: label, - execEvent: "", - class: "menuicon", - css: css, - enable() { return true; }, - run() { - changeToMarkInGroup(markType, view, groupMarks); - } - }); - } - createStar() { return new MenuItem({ title: "Summarize", @@ -772,6 +713,17 @@ export class TooltipTextMenu { return true; } + public static insertComment(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + createHighlightTool() { return new MenuItem({ title: "Highlight", @@ -975,24 +927,6 @@ export class TooltipTextMenu { return colorDropdown; } - deleteLinkItem() { - const icon = { - height: 16, width: 16, - path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" - }; - return new MenuItem({ - title: "Delete Link", - label: "X", - icon: icon, - css: "color: red", - class: "summarize", - execEvent: "", - run: (state, dispatch) => { - this.deleteLink(); - } - }); - } - createBrush(active: boolean = false) { const icon = { height: 32, width: 32, @@ -1020,8 +954,6 @@ export class TooltipTextMenu { }); } - // selectionchanged event handler - brush_function(state: EditorState, dispatch: any) { if (TooltipTextMenuManager.Instance._brushIsEmpty) { const selected_marks = this.getMarksInSelection(this.view.state); @@ -1055,8 +987,6 @@ export class TooltipTextMenu { } } } - - } createBrushDropdown(active: boolean = false) { @@ -1120,88 +1050,106 @@ export class TooltipTextMenu { return brushDom; } - // createCollapse() { - // this._collapseBtn = new MenuItem({ - // title: "Collapse", - // //label: "Collapse", - // icon: icons.join, - // execEvent: "", - // css: "color:white;", - // class: "summarize", - // run: () => { - // this.collapseToolTip(); - // } - // }); - // } - // collapseToolTip() { - // if (this._collapseBtn) { - // if (this._collapseBtn.spec.title === "Collapse") { - // // const newcollapseBtn = new MenuItem({ - // // title: "Expand", - // // icon: icons.join, - // // execEvent: "", - // // css: "color:white;", - // // class: "summarize", - // // run: (state, dispatch, view) => { - // // this.collapseToolTip(); - // // } - // // }); - // // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); - // // this._collapseBtn = newcollapseBtn; - // this.tooltip.style.width = "30px"; - // this._collapseBtn.spec.title = "Expand"; - // this._collapseBtn.render(this.view); - // } - // else { - // this._collapseBtn.spec.title = "Collapse"; - // this.tooltip.style.width = "550px"; - // this._collapseBtn.render(this.view); - // } - // } - // } - createLink() { - let markType = schema.marks.link; - return new MenuItem({ - title: "Add or remove link", - label: "Add or remove link", - execEvent: "", - icon: icons.link, - css: "color:white;", - class: "menuicon", - enable(state) { return !state.selection.empty; }, - run: (state, dispatch, view) => { - // to remove link - let curLink = ""; - if (this.markActive(state, markType)) { + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; - let { from, $from, to, empty } = state.selection; - let node = state.doc.nodeAt(from); - node && node.marks.map(m => { - m.type === markType && (curLink = m.attrs.href); - }); - //toggleMark(markType)(state, dispatch); - //return true; + //remove all other active font marks + fontMarks.forEach((type) => { + if (dispatch) { + if ($cursor) { + if (type.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(type)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, type); + } + for (let i of ranges) { + if (has) { + toggleMark(type)(view.state, view.dispatch, view); + } + } } - // to create link - openPrompt({ - title: "Create a link", - fields: { - href: new TextField({ - value: curLink, - label: "Link Target", - required: true - }), - title: new TextField({ label: "Title" }) - }, - callback(attrs: any) { - toggleMark(markType, attrs)(view.state, view.dispatch); - view.focus(); - }, - flyout_top: 0, - flyout_left: 0 - }); + } + }); + + if (markType) { + // fontsize + if (markType.name[0] === 'p') { + let size = this.fontSizeToNum.get(markType); + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } + } + } + else { + let fontName = this.fontStylesToName.get(markType); + if (fontName) { this.updateFontStyleDropdown(fontName); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else toggleMark(markType)(view.state, view.dispatch, view); + } + } + + //remove all node typeand apply the passed-in one to the selected text + changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { + //remove oldif (nodeType) { //add new + if (nodeType === schema.nodes.bullet_list) { + wrapInList(nodeType)(view.state, view.dispatch); + } else { + var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { + let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + view.dispatch(tx2); + })) { + let tx2 = view.state.tr; + let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + view.dispatch(tx3); + } + } + } + + //makes a button for the drop down FOR MARKS + //css is the style you want applied to the button + dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "dropdown-item", + css: css, + enable() { return true; }, + run() { + changeToMarkInGroup(markType, view, groupMarks); } }); } @@ -1213,7 +1161,7 @@ export class TooltipTextMenu { title: "", label: label, execEvent: "", - class: "menuicon", + class: "dropdown-item", css: css, enable() { return true; }, run() { @@ -1390,35 +1338,12 @@ export class TooltipTextMenu { update_mark_doms() { this.reset_mark_doms(); - let foundlink = false; - // let children = this.extras.childNodes; this._activeMarks.forEach((mark) => { if (this._marksToDoms.has(mark)) { let dom = this._marksToDoms.get(mark); if (dom) dom.style.color = "greenyellow"; } - // if (children.length > 1) { - // foundlink = true; - // } - // if (mark.type.name === "link" && children.length === 1) { - // // let del = document.createElement("button"); - // // del.textContent = "X"; - // // del.style.color = "red"; - // // del.style.height = "10px"; - // // del.style.width = "10px"; - // // del.style.marginLeft = "5px"; - // // del.onclick = this.deleteLink; - // // this.extras.appendChild(del); - // let del = this.deleteLinkItem().render(this.view).dom; - // this.extras.appendChild(del); - // foundlink = true; - // } }); - // if (!foundlink) { - // if (children.length > 1) { - // this.extras.removeChild(children[1]); - // } - // } // keeps brush tool highlighted if active when switching between textboxes if (!TooltipTextMenuManager.Instance._brushIsEmpty) { @@ -1535,30 +1460,11 @@ class TooltipTextMenuManager { return TooltipTextMenuManager._instance; } - // private pinnedToUnpinned() { - // let position = MainOverlayTextBox.Instance.position; - - // this.unpinnedX = this.pinnedX - position[0]; - // this.unpinnedY = this.pinnedY - position[1]; - // } - - // private unpinnedToPinned() { - // let position = MainOverlayTextBox.Instance.position; - - // this.pinnedX = position[0] + this.unpinnedX; - // this.pinnedY = position[1] + this.unpinnedY; - // } - public get isPinned() { return this._isPinned; } public toggleIsPinned() { - // if (this._isPinned) { - // this.pinnedToUnpinned(); - // } else { - // this.unpinnedToPinned(); - // } this._isPinned = !this._isPinned; } } -- cgit v1.2.3-70-g09d2 From 1f1707c8756c858e45e1f89cd0ff035231e15225 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 13:52:23 -0500 Subject: fixed errors --- .vscode/settings.json | 2 +- src/client/util/TooltipTextMenu.tsx | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'src/client/util') diff --git a/.vscode/settings.json b/.vscode/settings.json index c999af8b8..0b45c3f46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,5 @@ "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, "search.usePCRE2": true, - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 78917c779..0dc63668b 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -33,12 +33,10 @@ export class TooltipTextMenu { // editor state properties private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - - private fontStyles: MarkType[] = []; - private fontSizes: MarkType[] = []; + + private fontStyles: Mark[] = []; + private fontSizes: Mark[] = []; private listTypes: (NodeType | any)[] = []; - private fontSizeToNum: Map = new Map(); - private fontStylesToName: Map = new Map(); private listTypeToIcon: Map = new Map(); private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); -- cgit v1.2.3-70-g09d2 From 79c66275c60001e6d7aa997d9802ec0fed893205 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 15:03:08 -0500 Subject: fixed toolbar disappearing with multiple text boxes. --- src/client/util/TooltipTextMenu.tsx | 6 ++---- src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 0dc63668b..14f8b2bd4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,6 +1,4 @@ -import { library, dom } from '@fortawesome/fontawesome-svg-core'; -import { faListUl } from '@fortawesome/free-solid-svg-icons'; -import { action, observable } from "mobx"; +import { action } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; import { wrapInList } from 'prosemirror-schema-list'; @@ -1482,7 +1480,7 @@ export class TooltipTextMenu { } destroy() { - this.wrapper.remove(); + // this.wrapper.remove(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 49921170f..890c32bcb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -548,8 +548,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } public showTextBar = () => { - if (this.TextBar) { - TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && this.TextBar.appendChild(TooltipTextMenu.Toolbar); + if (this.TextBar && TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1) { + this.TextBar.appendChild(TooltipTextMenu.Toolbar); } } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0a535045b..35212b732 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; -- cgit v1.2.3-70-g09d2 From 2ab22e64e9b95e6b76566c493ae90f5951475e72 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 15:11:26 -0500 Subject: fixed naming of 'marker' mark --- src/client/util/RichTextSchema.tsx | 2 +- src/client/util/TooltipTextMenu.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0e8c9f14c..0a717dff1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -326,7 +326,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, - highlight2: { + marker: { attrs: { highlight: { default: "transparent" } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 14f8b2bd4..5136089b3 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -735,7 +735,7 @@ export class TooltipTextMenu { public static insertHighlight(color: String, state: EditorState, dispatch: any) { if (state.selection.empty) return false; - let highlightMark = state.schema.mark(state.schema.marks.highlight2, { highlight: color }); + let highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color }); dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); } @@ -1223,7 +1223,7 @@ export class TooltipTextMenu { }); } - markActive = function (state: EditorState, type: MarkType>) { + markActive = function(state: EditorState, type: MarkType>) { let { from, $from, to, empty } = state.selection; if (empty) return type.isInSet(state.storedMarks || $from.marks()); else return state.doc.rangeHasMark(from, to, type); -- cgit v1.2.3-70-g09d2