From 4ed6fd76752d60c617d9a396193ab6d8195dac1c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 10 Dec 2019 21:08:39 -0500 Subject: persistence daemon improvement, relaxed dummy gmail account security to allow messages --- src/server/ActionUtilities.ts | 6 +----- src/server/ApiManagers/SearchManager.ts | 2 +- src/server/ApiManagers/UtilManager.ts | 2 +- src/server/index.ts | 25 ------------------------- src/server/persistence_daemon.ts | 11 +++++++---- src/server/updateSearch.ts | 13 ++++++++++--- 6 files changed, 20 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 53ddea2fc..bc978c982 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -51,11 +51,7 @@ export async function log_execution({ startMessage, endMessage, action, color } catch (e) { error = e; } finally { - if (typeof endMessage === "string") { - log_helper(`${endMessage}.`, resolvedColor); - } else { - log_helper(`${endMessage({ result, error })}.`, resolvedColor); - } + log_helper(`${typeof endMessage === "string" ? endMessage : endMessage({ result, error })}.`, resolvedColor); } return result; } diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index ccfd570b8..37d66666b 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -72,7 +72,7 @@ export namespace SolrManager { const args = status ? "start" : "stop -p 8983"; try { console.log(`Solr management: trying to ${args}`); - console.log(await command_line(`solr.cmd ${args}`, "../../solr-8.1.1/bin")); + console.log(await command_line(`solr.cmd ${args}`, "./solr-8.3.1/bin")); return true; } catch (e) { console.log(red(`Solr management error: unable to ${args}`)); diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts index e959645e0..2f1bd956f 100644 --- a/src/server/ApiManagers/UtilManager.ts +++ b/src/server/ApiManagers/UtilManager.ts @@ -43,7 +43,7 @@ export default class UtilManager extends ApiManager { method: Method.GET, subscription: "/buxton", onValidation: async ({ res }) => { - const cwd = '../scraping/buxton'; + const cwd = './src/scraping/buxton'; const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); }; const onRejected = (err: any) => { console.error(err.message); res.send(err); }; diff --git a/src/server/index.ts b/src/server/index.ts index 3764eaabb..7671936a2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -119,31 +119,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); - let daemonInitialized = false; - addSupervisedRoute({ - method: Method.GET, - subscription: "/persist", - onValidation: async ({ res }) => { - if (!daemonInitialized) { - daemonInitialized = true; - log_execution({ - startMessage: "\ninitializing persistence daemon", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - if (!success) { - console.log(red("failed to initialize the persistance daemon")); - process.exit(0); - } - return "persistence daemon process closed"; - }, - action: async () => command_line("npx ts-node ./persistence_daemon.ts", "./src/server"), - color: yellow - }); - } - res.redirect("/home"); - } - }); - logRegistrationOutcome(); // initialize the web socket (bidirectional communication: if a user changes diff --git a/src/server/persistence_daemon.ts b/src/server/persistence_daemon.ts index 388440b49..2cb17456c 100644 --- a/src/server/persistence_daemon.ts +++ b/src/server/persistence_daemon.ts @@ -3,8 +3,9 @@ import { command_line, log_execution } from "./ActionUtilities"; import { red, yellow, cyan, green } from "colors"; import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; +import { Database } from "./database"; -const { LOCATION } = process.env; +const LOCATION = "http://localhost"; const recipient = "samuel_wilkins@brown.edu"; let restarting = false; @@ -39,13 +40,15 @@ async function listen() { console.log(await log_execution({ startMessage: "Initiating server restart", endMessage: "Server successfully restarted", - action: async () => command_line(`npm run start${suffix}`, "../../"), + action: () => command_line(`npm run start${suffix}`), color: green })); restarting = false; + } else { + console.log(green(`No issues detected as of ${new Date().toISOString()}`)); } } - }, 1000 * 90); + }, 1000 * 10); } function emailText(error: any) { @@ -72,7 +75,7 @@ async function notify(error: any) { text: emailText(error) } as MailOptions; return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); }); } diff --git a/src/server/updateSearch.ts b/src/server/updateSearch.ts index 5ae6885c5..83094d36a 100644 --- a/src/server/updateSearch.ts +++ b/src/server/updateSearch.ts @@ -59,7 +59,14 @@ async function update() { }); const cursor = await log_execution({ startMessage: "Connecting to and querying for all documents from database...", - endMessage: "Connection successful and query complete", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + if (!success) { + console.log(red("Unable to connect to the database.")); + process.exit(0); + } + return "Connection successful and query complete"; + }, action: () => Database.Instance.query({}), color: yellow }); @@ -92,7 +99,7 @@ async function update() { updates.push(update); } } - await cursor.forEach(updateDoc); + await cursor?.forEach(updateDoc); const result = await log_execution({ startMessage: `Dispatching updates for ${updates.length} documents`, endMessage: "Dispatched updates complete", @@ -107,7 +114,7 @@ async function update() { console.log(result); console.log("\n"); } - await cursor.close(); + await cursor?.close(); process.exit(0); } -- cgit v1.2.3-70-g09d2 From 7a5c3d931381cc698d01b710a2576117449cf302 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Dec 2019 22:39:43 -0500 Subject: cleaned up userMarks a bit (got rid of 'opened' attr). added @ markup to create collection from text selection --- src/client/util/DocumentManager.ts | 2 ++ src/client/util/RichTextRules.ts | 20 +++++++++++++++++++- src/client/util/RichTextSchema.tsx | 10 ++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 13 ++++++++----- src/client/views/nodes/FormattedTextBoxComment.tsx | 19 +++++++++---------- 6 files changed, 41 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index d491cd1b1..07247b7e9 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -194,6 +194,8 @@ export class DocumentManager { const target = linkFollowDocs[reverse ? 1 : 0]; target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]); DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]); + } else if (link) { + DocumentManager.Instance.jumpToDocument(link, zoom, (doc: Doc) => focus(doc, "onRight"), undefined, undefined); } } diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 94bfc5ef2..c69112b3b 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -6,8 +6,10 @@ import { NumCast, Cast } from "../../new_fields/Types"; import { Doc } from "../../new_fields/Doc"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { TooltipTextMenuManager } from "../util/TooltipTextMenu"; -import { Docs } from "../documents/Documents"; +import { Docs, DocUtils } from "../documents/Documents"; import { Id } from "../../new_fields/FieldSymbols"; +import { DocServer } from "../DocServer"; +import { returnFalse, Utils } from "../../Utils"; export const inpRules = { rules: [ @@ -172,6 +174,22 @@ export const inpRules = { if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; }), + new InputRule( + new RegExp(/@$/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from) return null; + + const value = state.doc.textBetween(start, end); + if (value) { + DocServer.GetRefField(value).then(docx => { + let doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + DocUtils.Publish(doc, value, returnFalse, returnFalse); + }); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value }); + return state.tr.addMark(start, end, link); + } + return state.tr; + }), new InputRule( new RegExp(/^\^\^\s$/), (state, match, start, end) => { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index fac8f4027..c6314bc30 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -462,7 +462,6 @@ export const marks: { [index: string]: MarkSpec } = { user_mark: { attrs: { userid: { default: "" }, - opened: { default: true }, modified: { default: "when?" }, // 5 second intervals since 1970 }, group: "inline", @@ -472,16 +471,13 @@ export const marks: { [index: string]: MarkSpec } = { const hr = Math.round(min / 60); const day = Math.round(hr / 60 / 24); const remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; - return node.attrs.opened ? - ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] : - ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]]; + return ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0]; } }, // the id of the user who entered the text user_tag: { attrs: { userid: { default: "" }, - opened: { default: true }, modified: { default: "when?" }, // 5 second intervals since 1970 tag: { default: "" } }, @@ -489,9 +485,7 @@ export const marks: { [index: string]: MarkSpec } = { inclusive: false, toDOM(node: any) { const uid = node.attrs.userid.replace(".", "").replace("@", ""); - return node.attrs.opened ? - ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] : - ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]]; + return ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0]; } }, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 727d631c4..24cc0b5ea 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -644,7 +644,7 @@ export class DocumentView extends DocComponent(Docu contents={this.Document[showTitle]} display={"block"} height={72} fontSize={12} GetValue={() => StrCast(this.Document[showTitle])} - SetValue={(value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true} + SetValue={undoBatch((value: string) => (Doc.GetProto(this.Document)[showTitle] = value) ? true : true)} /> ); return <> diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 70fa4974d..9318142e2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -184,7 +184,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.linkOnDeselect.set(key, value); const id = Utils.GenerateDeterministicGuid(this.dataDoc[Id] + key); - const link = this._editorView.state.schema.marks.link.create({ href: `http://localhost:1050/doc/${id}`, location: "onRight", title: value }); + const link = this._editorView.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + id), location: "onRight", title: value }); const mval = this._editorView.state.schema.marks.metadataVal.create(); const offset = (tx.selection.to === range!.end - 1 ? -1 : 0); tx = tx.addMark(textEndSelection - value.length + offset, textEndSelection, link).addMark(textEndSelection - value.length + offset, textEndSelection, mval); @@ -238,9 +238,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); } } - setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { + adoptAnnotation = (start: number, end: number, mark: Mark) => { const view = this._editorView!; - const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); + const nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: Doc.CurrentUserEmail }); view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); } protected createDropTarget = (ele: HTMLDivElement) => { @@ -873,6 +873,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & static _downEvent: any; onPointerDown = (e: React.PointerEvent): void => { + this.doLinkOnDeselect(); FormattedTextBox._downEvent = true; FormattedTextBoxComment.textBox = this; if (this.props.onClick && e.button === 0) { @@ -1053,9 +1054,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this._undoTyping.end(); this._undoTyping = undefined; } - this.doLinkOnDeselect(); 6 + this.doLinkOnDeselect(); } + _lastTimedMark: Mark | undefined = undefined; onKeyPress = (e: React.KeyboardEvent) => { if (e.altKey) { e.preventDefault(); @@ -1078,7 +1080,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); + const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark! : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); + this._lastTimedMark = mark; this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); if (!this._undoTyping) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 409229c1a..1755cb99c 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -57,7 +57,6 @@ export class FormattedTextBoxComment { static start: number; static end: number; static mark: Mark; - static opened: boolean; static textBox: FormattedTextBox | undefined; static linkDoc: Doc | undefined; constructor(view: any) { @@ -89,10 +88,8 @@ export class FormattedTextBoxComment { } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight"); } - FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; - textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( - FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, - FormattedTextBoxComment.opened, keep); + keep && textBox && FormattedTextBoxComment.start !== undefined && textBox.adoptAnnotation( + FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark); e.stopPropagation(); e.preventDefault(); }; @@ -104,12 +101,11 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.textBox = undefined; FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); } - public static SetState(textBox: any, opened: boolean, start: number, end: number, mark: Mark) { + public static SetState(textBox: any, start: number, end: number, mark: Mark) { FormattedTextBoxComment.textBox = textBox; FormattedTextBoxComment.start = start; FormattedTextBoxComment.end = end; FormattedTextBoxComment.mark = mark; - FormattedTextBoxComment.opened = opened; FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); } @@ -143,7 +139,7 @@ export class FormattedTextBoxComment { state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); const mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); + FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } if (mark && child && ((nbef && naft) || !noselection)) { FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); @@ -160,19 +156,22 @@ export class FormattedTextBoxComment { const 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.startsWith("https://en.wikipedia.org/wiki/")) { wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); } else { FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; } - (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { + FormattedTextBoxComment.tooltipText.textContent = "target not found..."; + (FormattedTextBoxComment.tooltipText as any).href = ""; const docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { if (linkDoc instanceof Doc) { + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; FormattedTextBoxComment.linkDoc = linkDoc; - const 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)) || linkDoc); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); } catch (e) { } -- cgit v1.2.3-70-g09d2 From 921554d7d585a1138c7cf8d02d1e46f47c7cafe7 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Dec 2019 23:27:55 -0500 Subject: added indenting for ordered lists --- src/client/util/RichTextRules.ts | 4 ++++ src/client/util/RichTextSchema.tsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index c69112b3b..ba2bc785d 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -149,6 +149,10 @@ export const inpRules = { (state, match, start, end) => { if (state.selection.to === state.selection.from) return null; const pos = (state.doc.resolve(start) as any); + if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) { + let node = (state.selection as NodeSelection).node; + return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: 30 }); + } let depth = pos.path.length / 3 - 1; for (; depth >= 0; depth--) { if (pos.node(depth).type === schema.nodes.paragraph) { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index c6314bc30..f9251fb7e 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -229,14 +229,15 @@ export const nodes: { [index: string]: NodeSpec } = { setFontSize: { default: undefined }, setFontFamily: { default: "inherit" }, inheritedFontSize: { default: undefined }, - visibility: { default: true } + visibility: { default: true }, + indent: { default: undefined } }, toDOM(node: Node) { if (node.attrs.mapStyle === "bullet") return ['ul', 0]; const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; const ffam = node.attrs.setFontFamily; - return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] : + return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; margin-left: ${node.attrs.indent}` }, 0] : ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }]; } }, -- cgit v1.2.3-70-g09d2 From 011c777f4109afffd335ba3f447e258e1ad53ddf Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 10 Dec 2019 23:38:26 -0500 Subject: made indent & other alignment commands toggles --- src/client/util/RichTextRules.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index ba2bc785d..22b2a8204 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -123,8 +123,9 @@ export const inpRules = { const pos = (state.doc.resolve(start) as any); let depth = pos.path.length / 3 - 1; for (; depth >= 0; depth--) { - if (pos.node(depth).type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, indent: 25 }); + let node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } } @@ -137,8 +138,9 @@ export const inpRules = { const pos = (state.doc.resolve(start) as any); let depth = pos.path.length / 3 - 1; for (; depth >= 0; depth--) { - if (pos.node(depth).type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, indent: -25 }); + let node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } } @@ -151,12 +153,13 @@ export const inpRules = { const pos = (state.doc.resolve(start) as any); if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) { let node = (state.selection as NodeSelection).node; - return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: 30 }); + return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); } let depth = pos.path.length / 3 - 1; for (; depth >= 0; depth--) { - if (pos.node(depth).type === schema.nodes.paragraph) { - const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, pos.node(depth).type, { ...pos.node(depth).attrs, inset: 30 }); + let node = pos.node(depth); + if (node.type === schema.nodes.paragraph) { + const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } } -- cgit v1.2.3-70-g09d2 From 6665483b80ff6874cf9cc7c9cb3f7e58fcec20ca Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 10 Dec 2019 23:40:42 -0500 Subject: persistance daemon improvements --- ...npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT | 0 package.json | 3 +- src/server/ActionUtilities.ts | 13 ++- src/server/ProcessManager.ts | 45 +++++++++++ src/server/daemon/persistence_daemon.ts | 92 ++++++++++++++++++++++ src/server/index.ts | 13 ++- src/server/persistence_daemon.ts | 82 ------------------- 7 files changed, 163 insertions(+), 85 deletions(-) create mode 100644 logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT create mode 100644 src/server/ProcessManager.ts create mode 100644 src/server/daemon/persistence_daemon.ts delete mode 100644 src/server/persistence_daemon.ts (limited to 'src') diff --git a/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT b/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json index 1689194db..97563e137 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", - "start": "cross-env LOCATION=http://localhost NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "start-spawn": "cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", "test": "mocha -r ts-node/register test/**/*.ts", diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index bc978c982..2173f4369 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -1,12 +1,16 @@ import * as fs from 'fs'; import { ExecOptions } from 'shelljs'; -import { exec } from 'child_process'; +import { exec, spawn } from 'child_process'; import * as path from 'path'; import * as rimraf from "rimraf"; import { yellow, Color } from 'colors'; const projectRoot = path.resolve(__dirname, "../../"); +export function pathFromRoot(relative: string) { + return path.resolve(projectRoot, relative); +} + export const command_line = (command: string, fromDirectory?: string) => { return new Promise((resolve, reject) => { const options: ExecOptions = {}; @@ -17,6 +21,13 @@ export const command_line = (command: string, fromDirectory?: string) => { }); }; +export async function spawn_detached_process(command: string, args?: readonly string[]) { + const out = path.resolve(projectRoot, `./logs/${command}-${process.pid}-${new Date().toUTCString()}`); + const child_out = fs.openSync(out, 'a'); + const child_error = fs.openSync(out, 'a'); + spawn(command, args, { detached: true, stdio: ["ignore", child_out, child_error] }).unref(); +} + export const read_text_file = (relativePath: string) => { const target = path.resolve(__dirname, relativePath); return new Promise((resolve, reject) => { diff --git a/src/server/ProcessManager.ts b/src/server/ProcessManager.ts new file mode 100644 index 000000000..2237f9e1b --- /dev/null +++ b/src/server/ProcessManager.ts @@ -0,0 +1,45 @@ +import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs"; +import { pathFromRoot, log_execution, spawn_detached_process } from './ActionUtilities'; +import { resolve } from "path"; +import { red, yellow } from "colors"; + +const daemonPath = pathFromRoot("./src/server/daemon/persistence_daemon.ts"); + +export namespace ProcessManager { + + export async function initialize() { + const logPath = pathFromRoot("./logs"); + const filePath = resolve(logPath, "./server_pids.txt"); + const exists = existsSync(logPath); + if (exists) { + unlinkSync(filePath); + } else { + mkdirSync(logPath); + } + const { pid } = process; + if (process.env.SPAWNED === "true") { + writeFileSync(filePath, `${pid} created at ${new Date().toUTCString()}\n`); + } + } + + let daemonInitialized = false; + export async function trySpawnDaemon() { + if (!daemonInitialized) { + daemonInitialized = true; + await log_execution({ + startMessage: "\ninitializing persistence daemon", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + if (!success) { + console.log(red("failed to initialize the persistance daemon")); + process.exit(0); + } + return "persistence daemon process closed"; + }, + action: () => spawn_detached_process("npx ts-node", [daemonPath]), + color: yellow + }); + } + } + +} \ No newline at end of file diff --git a/src/server/daemon/persistence_daemon.ts b/src/server/daemon/persistence_daemon.ts new file mode 100644 index 000000000..3eb17a9b4 --- /dev/null +++ b/src/server/daemon/persistence_daemon.ts @@ -0,0 +1,92 @@ +import * as request from "request-promise"; +import { log_execution, spawn_detached_process } from "../ActionUtilities"; +import { red, yellow, cyan, green } from "colors"; +import * as nodemailer from "nodemailer"; +import { MailOptions } from "nodemailer/lib/json-transport"; +import { writeFileSync } from "fs"; +import { resolve } from 'path'; + +const LOCATION = "http://localhost"; +const recipient = "samuel_wilkins@brown.edu"; +let restarting = false; + +writeFileSync(resolve(__dirname, "./current_pid.txt"), process.pid); + +function timestamp() { + return `@ ${new Date().toISOString()}`; +} + +async function listen() { + if (!LOCATION) { + console.log(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); + process.exit(0); + } + const heartbeat = `${LOCATION}:1050/serverHeartbeat`; + // if this is on our remote server, the server must be run in release mode + // const suffix = LOCATION.includes("localhost") ? "" : "-release"; + setInterval(async () => { + let error: any; + try { + await request.get(heartbeat); + } catch (e) { + error = e; + } finally { + if (error) { + if (!restarting) { + restarting = true; + console.log(yellow("Detected a server crash!")); + await log_execution({ + startMessage: "Sending crash notification email", + endMessage: ({ error, result }) => { + const success = error === null && result === true; + return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient; + }, + action: async () => notify(error || "Hmm, no error to report..."), + color: cyan + }); + console.log(await log_execution({ + startMessage: "Initiating server restart", + endMessage: "Server successfully restarted", + action: () => spawn_detached_process(`npm run start-spawn`), + color: green + })); + restarting = false; + } else { + console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + } + } else { + console.log(green(`No issues detected ${timestamp()}`)); + } + } + }, 1000 * 10); +} + +function emailText(error: any) { + return [ + `Hey ${recipient.split("@")[0]},`, + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `Location: ${LOCATION}\nError: ${error}`, + "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." + ].join("\n\n"); +} + +async function notify(error: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject: 'Dash Server Crash', + text: emailText(error) + } as MailOptions; + return new Promise(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); + }); +} + +listen(); \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 7671936a2..795418b31 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -18,11 +18,12 @@ import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; -import { log_execution, command_line } from "./ActionUtilities"; +import { log_execution } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { yellow, red } from "colors"; import { disconnect } from "../server/Initialization"; +import { ProcessManager } from "./ProcessManager"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); @@ -35,6 +36,7 @@ export const ExitHandlers = new Array<() => void>(); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { + await ProcessManager.initialize(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); @@ -119,6 +121,15 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); + addSupervisedRoute({ + method: Method.GET, + subscription: "/persist", + onValidation: ({ res }) => { + ProcessManager.trySpawnDaemon(); + res.redirect("/home"); + } + }); + logRegistrationOutcome(); // initialize the web socket (bidirectional communication: if a user changes diff --git a/src/server/persistence_daemon.ts b/src/server/persistence_daemon.ts deleted file mode 100644 index 2cb17456c..000000000 --- a/src/server/persistence_daemon.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as request from "request-promise"; -import { command_line, log_execution } from "./ActionUtilities"; -import { red, yellow, cyan, green } from "colors"; -import * as nodemailer from "nodemailer"; -import { MailOptions } from "nodemailer/lib/json-transport"; -import { Database } from "./database"; - -const LOCATION = "http://localhost"; -const recipient = "samuel_wilkins@brown.edu"; -let restarting = false; - -async function listen() { - if (!LOCATION) { - console.log(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); - process.exit(0); - } - const heartbeat = `${LOCATION}:1050/serverHeartbeat`; - // if this is on our remote server, the server must be run in release mode - const suffix = LOCATION.includes("localhost") ? "" : "-release"; - setInterval(async () => { - let response: any; - let error: any; - try { - response = await request.get(heartbeat); - } catch (e) { - error = e; - } finally { - if (!response && !restarting) { - restarting = true; - console.log(yellow("Detected a server crash!")); - await log_execution({ - startMessage: "Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - console.log(await log_execution({ - startMessage: "Initiating server restart", - endMessage: "Server successfully restarted", - action: () => command_line(`npm run start${suffix}`), - color: green - })); - restarting = false; - } else { - console.log(green(`No issues detected as of ${new Date().toISOString()}`)); - } - } - }, 1000 * 10); -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${LOCATION}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); - }); -} - -listen(); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ad079a088ae9262a4a40a2f0d2a2c5d948140492 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Dec 2019 03:57:09 -0500 Subject: somewhat functional daemon --- logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log | 274 +++++++++++++++++++++ ...npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT | 0 logs/server_pids.txt | 1 + src/server/ActionUtilities.ts | 22 +- src/server/ProcessManager.ts | 49 ++-- src/server/daemon/current_daemon_pid.txt | 1 + src/server/daemon/persistence_daemon.ts | 53 ++-- .../session_crashes_@ 2019-12-11T08:31:56.281Z.log | 1 + .../session_crashes_@ 2019-12-11T08:43:46.454Z.log | 2 + 9 files changed, 357 insertions(+), 46 deletions(-) create mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log delete mode 100644 logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT create mode 100644 logs/server_pids.txt create mode 100644 src/server/daemon/current_daemon_pid.txt create mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log create mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log (limited to 'src') diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log new file mode 100644 index 000000000..37e232d48 --- /dev/null +++ b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log @@ -0,0 +1,274 @@ + +> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web +> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts + +Using ts-node version 7.0.1, typescript version 3.7.2 +objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined. + +starting execution of preliminary functions... +(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect. +completed preliminary functions +. +Starting type checking and linting service... +Using 1 worker with 2048MB memory limit + +running server in development mode + +registering server routes... +all server routes have been successfully registered: +/ +/activity +/buxton +/delete +/deleteAll +/deleteWithAux +/deleteWithGoogleCredentials +/doc/:docId +/downloadId/:docId +/environment/:key +/getCurrentUser +/getUserDocumentId +/getUsers +/googleDocs/:sector/:action +/googlePhotosMediaDownload +/googlePhotosMediaUpload +/home +/imageHierarchyExport/:docId +/inspectImage +/persist +/pull +/readGoogleAccessToken +/search +/serializeDoc/:docId +/serverHeartbeat +/shutdown +/solr/:action +/textsearch +/thumbnail/:filename +/upload +/uploadDoc +/uploadURI +/version +/writeGoogleAccessToken + +websocket listening on port 4321 +server listening on port 1050 + +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +user samuel_wilkins@brown.edu has connected to the web socket +user samuel_wilkins@brown.edu has connected to the web socket +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +user samuel_wilkins@brown.edu has connected to the web socket +user samuel_wilkins@brown.edu has connected to the web socket +Type checking and linting in progress... +webpack built 8f6b743d91fd3862683b in 47419ms +⚠ 「wdm」: Hash: 8f6b743d91fd3862683b +Version: webpack 4.36.1 +Time: 47419ms +Built at: 12/11/2019 3:45:31 AM + Asset Size Chunks  Chunk Names +275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes  [emitted] +32f1593298e6e7bee5673bf647328d72.png 429 bytes  [emitted] +718c914a99a2136c01c84e01f63e505a.png 829 bytes  [emitted] +906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes  [emitted] + assets/downarrow.png 3.28 KiB  [emitted] + assets/env.json 360 bytes  [emitted] + assets/google_photos.png 114 KiB  [emitted] + assets/google_tags.png 7.9 KiB  [emitted] + assets/loading.gif 112 KiB  [emitted] + assets/pdf.worker.js 1.55 MiB  [emitted] + bundle.js 20.8 MiB bundle [emitted] bundle + bundle.js.map 23.3 MiB bundle [emitted] bundle + debug/repl.html 348 bytes  [emitted] + debug/test.html 245 bytes  [emitted] + debug/viewer.html 357 bytes  [emitted] +e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes  [emitted] + imageUpload.js 20.8 MiB imageUpload [emitted] imageUpload + imageUpload.js.map 23.3 MiB imageUpload [emitted] imageUpload + index.html 593 bytes  [emitted] + inkControls.js 116 KiB inkControls [emitted] inkControls + inkControls.js.map 122 KiB inkControls [emitted] inkControls + mobile/image.html 333 bytes  [emitted] + mobile/ink.html 252 bytes  [emitted] + repl.js 9.31 MiB repl [emitted] repl + repl.js.map 10.5 MiB repl [emitted] repl + test.js 1.2 MiB test [emitted] test + test.js.map 1.42 MiB test [emitted] test + test.pdf 53.6 KiB  [emitted] + vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker + vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker + viewer.js 9.47 MiB viewer [emitted] viewer + viewer.js.map 10.7 MiB viewer [emitted] viewer +Entrypoint bundle = bundle.js bundle.js.map +Entrypoint viewer = viewer.js viewer.js.map +Entrypoint repl = repl.js repl.js.map +Entrypoint test = test.js test.js.map +Entrypoint inkControls = inkControls.js inkControls.js.map +Entrypoint imageUpload = imageUpload.js imageUpload.js.map +[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} [built] +[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} [built] +[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} [built] +[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} [built] +[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} [built] +[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} [built] + [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} [built] + [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} [built] + [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} [built] + [./src/client/views/Main.tsx] 4.03 KiB {bundle} [built] + [./src/debug/Repl.tsx] 6.87 KiB {repl} [built] + [./src/debug/Test.tsx] 1.02 KiB {test} [built] + [./src/debug/Viewer.tsx] 12.1 KiB {viewer} [built] + [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} [built] + [./src/mobile/InkControls.tsx] 14 bytes {inkControls} [built] + + 1494 hidden modules + +WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 +Critical dependency: the request of a dependency is an expression + @ ./src/client/util/Scripting.ts + @ ./src/debug/Viewer.tsx + @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true +ℹ 「wdm」: Compiled with warnings. +ℹ 「wdm」: Compiling... +webpack building... +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +Type checking and linting in progress... +webpack built 8f6b743d91fd3862683b in 615ms +⚠ 「wdm」: Hash: 8f6b743d91fd3862683b +Version: webpack 4.36.1 +Time: 615ms +Built at: 12/11/2019 3:45:33 AM + Asset Size Chunks Chunk Names +275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes   +32f1593298e6e7bee5673bf647328d72.png 429 bytes   +718c914a99a2136c01c84e01f63e505a.png 829 bytes   +906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes   + bundle.js 20.8 MiB bundle bundle + bundle.js.map 23.3 MiB bundle bundle +e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes   + imageUpload.js 20.8 MiB imageUpload imageUpload + imageUpload.js.map 23.3 MiB imageUpload imageUpload + inkControls.js 116 KiB inkControls inkControls + inkControls.js.map 122 KiB inkControls inkControls + repl.js 9.31 MiB repl repl + repl.js.map 10.5 MiB repl repl + test.js 1.2 MiB test test + test.js.map 1.42 MiB test test + vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker vendors~pdfjsWorker + vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker vendors~pdfjsWorker + viewer.js 9.47 MiB viewer viewer + viewer.js.map 10.7 MiB viewer viewer +Entrypoint bundle = bundle.js bundle.js.map +Entrypoint viewer = viewer.js viewer.js.map +Entrypoint repl = repl.js repl.js.map +Entrypoint test = test.js test.js.map +Entrypoint inkControls = inkControls.js inkControls.js.map +Entrypoint imageUpload = imageUpload.js imageUpload.js.map +[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} +[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} +[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} +[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} +[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} +[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} + [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} + [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} + [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} + [./src/client/views/Main.tsx] 4.03 KiB {bundle} + [./src/debug/Repl.tsx] 6.87 KiB {repl} + [./src/debug/Test.tsx] 1.02 KiB {test} + [./src/debug/Viewer.tsx] 12.1 KiB {viewer} + [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} + [./src/mobile/InkControls.tsx] 14 bytes {inkControls} + + 1494 hidden modules + +WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 +Critical dependency: the request of a dependency is an expression + @ ./src/client/util/Scripting.ts + @ ./src/debug/Viewer.tsx + @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true +ℹ 「wdm」: Compiled with warnings. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21): +prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25): +prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21): +prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25): +prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36): +triple-equals: == should be === +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21): +prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21): +prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66): +no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33): +no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29): +prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21): +prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21): +prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13): +prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13): +prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13): +prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21): +prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13): +prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25): +prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13): +prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13): +prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13): +prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17): +prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21): +prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17): +prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25): +prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17): +prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'. +No type errors found +Version: typescript 3.7.2, tslint 5.18.0 +Time: 5473ms diff --git a/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT b/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT deleted file mode 100644 index e69de29bb..000000000 diff --git a/logs/server_pids.txt b/logs/server_pids.txt new file mode 100644 index 000000000..2aa143f24 --- /dev/null +++ b/logs/server_pids.txt @@ -0,0 +1 @@ +9675 created at Wed, 11 Dec 2019 08:44:28 GMT diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 2173f4369..9bdc4ed93 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -1,12 +1,11 @@ -import * as fs from 'fs'; +import { readFile, writeFile, exists, mkdir, unlink } from 'fs'; import { ExecOptions } from 'shelljs'; -import { exec, spawn } from 'child_process'; +import { exec } from 'child_process'; import * as path from 'path'; import * as rimraf from "rimraf"; import { yellow, Color } from 'colors'; const projectRoot = path.resolve(__dirname, "../../"); - export function pathFromRoot(relative: string) { return path.resolve(projectRoot, relative); } @@ -21,24 +20,17 @@ export const command_line = (command: string, fromDirectory?: string) => { }); }; -export async function spawn_detached_process(command: string, args?: readonly string[]) { - const out = path.resolve(projectRoot, `./logs/${command}-${process.pid}-${new Date().toUTCString()}`); - const child_out = fs.openSync(out, 'a'); - const child_error = fs.openSync(out, 'a'); - spawn(command, args, { detached: true, stdio: ["ignore", child_out, child_error] }).unref(); -} - export const read_text_file = (relativePath: string) => { const target = path.resolve(__dirname, relativePath); return new Promise((resolve, reject) => { - fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString())); + readFile(target, (err, data) => err ? reject(err) : resolve(data.toString())); }); }; export const write_text_file = (relativePath: string, contents: any) => { const target = path.resolve(__dirname, relativePath); return new Promise((resolve, reject) => { - fs.writeFile(target, contents, (err) => err ? reject(err) : resolve()); + writeFile(target, contents, (err) => err ? reject(err) : resolve()); }); }; @@ -93,10 +85,10 @@ export function msToTime(duration: number) { } export const createIfNotExists = async (path: string) => { - if (await new Promise(resolve => fs.exists(path, resolve))) { + if (await new Promise(resolve => exists(path, resolve))) { return true; } - return new Promise(resolve => fs.mkdir(path, error => resolve(error === null))); + return new Promise(resolve => mkdir(path, error => resolve(error === null))); }; export async function Prune(rootDirectory: string): Promise { @@ -104,4 +96,4 @@ export async function Prune(rootDirectory: string): Promise { return error === null; } -export const Destroy = (mediaPath: string) => new Promise(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +export const Destroy = (mediaPath: string) => new Promise(resolve => unlink(mediaPath, error => resolve(error === null))); diff --git a/src/server/ProcessManager.ts b/src/server/ProcessManager.ts index 2237f9e1b..671f0a234 100644 --- a/src/server/ProcessManager.ts +++ b/src/server/ProcessManager.ts @@ -1,7 +1,9 @@ -import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs"; -import { pathFromRoot, log_execution, spawn_detached_process } from './ActionUtilities'; -import { resolve } from "path"; -import { red, yellow } from "colors"; +import { existsSync, mkdirSync, createWriteStream } from "fs"; +import { pathFromRoot, log_execution } from './ActionUtilities'; +import { red, green } from "colors"; +import rimraf = require("rimraf"); +import { ChildProcess, spawn } from "child_process"; +import { Stream } from "stream"; const daemonPath = pathFromRoot("./src/server/daemon/persistence_daemon.ts"); @@ -9,22 +11,33 @@ export namespace ProcessManager { export async function initialize() { const logPath = pathFromRoot("./logs"); - const filePath = resolve(logPath, "./server_pids.txt"); - const exists = existsSync(logPath); - if (exists) { - unlinkSync(filePath); - } else { - mkdirSync(logPath); + if (existsSync(logPath)) { + if (!process.env.SPAWNED) { + await new Promise(resolve => rimraf(logPath, resolve)); + } } - const { pid } = process; - if (process.env.SPAWNED === "true") { - writeFileSync(filePath, `${pid} created at ${new Date().toUTCString()}\n`); + mkdirSync(logPath); + } + + function generate_log_name(command: string, args?: readonly string[]) { + return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`); + } + + export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; + + export async function spawn_detached(command: string, args?: readonly string[], out?: Sink): Promise { + if (!out) { + const logStream = createWriteStream(generate_log_name(command, args)); + out = await new Promise(resolve => logStream.on("open", resolve)); } + const child = spawn(command, args, { detached: true, stdio: ["ignore", out, out] }); + child.unref(); + return child; } let daemonInitialized = false; export async function trySpawnDaemon() { - if (!daemonInitialized) { + if (!process.env.SPAWNED && !daemonInitialized) { daemonInitialized = true; await log_execution({ startMessage: "\ninitializing persistence daemon", @@ -32,13 +45,15 @@ export namespace ProcessManager { const success = error === null && result !== undefined; if (!success) { console.log(red("failed to initialize the persistance daemon")); + console.log(error); process.exit(0); } - return "persistence daemon process closed"; + return "failsafe daemon process successfully spawned"; }, - action: () => spawn_detached_process("npx ts-node", [daemonPath]), - color: yellow + action: () => spawn_detached('npx', ['ts-node', daemonPath], process.stdout), + color: green }); + console.log(); } } diff --git a/src/server/daemon/current_daemon_pid.txt b/src/server/daemon/current_daemon_pid.txt new file mode 100644 index 000000000..f3cd0298c --- /dev/null +++ b/src/server/daemon/current_daemon_pid.txt @@ -0,0 +1 @@ +9626 \ No newline at end of file diff --git a/src/server/daemon/persistence_daemon.ts b/src/server/daemon/persistence_daemon.ts index 3eb17a9b4..099c7898c 100644 --- a/src/server/daemon/persistence_daemon.ts +++ b/src/server/daemon/persistence_daemon.ts @@ -1,22 +1,41 @@ import * as request from "request-promise"; -import { log_execution, spawn_detached_process } from "../ActionUtilities"; -import { red, yellow, cyan, green } from "colors"; +import { log_execution, pathFromRoot } from "../ActionUtilities"; +import { red, yellow, cyan, green, Color } from "colors"; import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; -import { writeFileSync } from "fs"; +import { writeFileSync, appendFileSync, createWriteStream, existsSync } from "fs"; import { resolve } from 'path'; +import { ChildProcess } from "child_process"; +import { ProcessManager } from "../ProcessManager"; + +console.log(yellow("Initializing daemon...")); + +process.on('SIGINT', () => current_backup?.kill("SIGTERM")); + +const crashLogPath = resolve(__dirname, `./session_crashes_${timestamp()}.log`); +function addLogEntry(message: string, color: Color) { + const formatted = color(`${message} ${timestamp()}.`); + console.log(formatted); + appendFileSync(crashLogPath, `${formatted}\n`); +} const LOCATION = "http://localhost"; const recipient = "samuel_wilkins@brown.edu"; let restarting = false; -writeFileSync(resolve(__dirname, "./current_pid.txt"), process.pid); +const frequency = 10; +const { pid } = process; +writeFileSync(resolve(__dirname, "./current_daemon_pid.txt"), pid); +console.log(cyan(`${pid} written to ./current_daemon_pid.txt`)); function timestamp() { return `@ ${new Date().toISOString()}`; } +let current_backup: ChildProcess | undefined = undefined; + async function listen() { + console.log(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); if (!LOCATION) { console.log(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); process.exit(0); @@ -28,34 +47,40 @@ async function listen() { let error: any; try { await request.get(heartbeat); + if (restarting) { + addLogEntry("Backup server successfully restarted", green); + } + restarting = false; } catch (e) { error = e; } finally { if (error) { if (!restarting) { restarting = true; - console.log(yellow("Detected a server crash!")); + addLogEntry("Detected a server crash", red); + current_backup?.kill(); await log_execution({ startMessage: "Sending crash notification email", endMessage: ({ error, result }) => { const success = error === null && result === true; - return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient; + return `${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; }, action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - console.log(await log_execution({ + current_backup = await log_execution({ startMessage: "Initiating server restart", - endMessage: "Server successfully restarted", - action: () => spawn_detached_process(`npm run start-spawn`), + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + return success ? "Child process spawned.." : `An error occurred while attempting to restart the server:\n${error}`; + }, + action: () => ProcessManager.spawn_detached('npm', ['run', 'start-spawn']), color: green - })); - restarting = false; + }); + writeFileSync(pathFromRoot("./logs/current_server_pid.txt"), `${current_backup?.pid ?? -1} created ${timestamp()}\n`); } else { console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); } - } else { - console.log(green(`No issues detected ${timestamp()}`)); } } }, 1000 * 10); @@ -85,7 +110,7 @@ async function notify(error: any) { text: emailText(error) } as MailOptions; return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); }); } diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log new file mode 100644 index 000000000..32b7810ea --- /dev/null +++ b/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log @@ -0,0 +1 @@ +Detected a server crash @ 2019-12-11T08:32:36.317Z diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log new file mode 100644 index 000000000..ebb6843c2 --- /dev/null +++ b/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log @@ -0,0 +1,2 @@ +Detected a server crash @ 2019-12-11T08:44:26.494Z. +Backup server successfully restarted @ 2019-12-11T08:45:33.644Z. -- cgit v1.2.3-70-g09d2 From ae3603e26adb635380d530b84cb9d6f1284066ef Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Dec 2019 13:54:28 -0500 Subject: process factory refactor --- .gitignore | 1 + logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log | 274 --------------------- logs/server_pids.txt | 1 - src/server/ActionUtilities.ts | 9 +- src/server/ChildProcessUtilities/ProcessFactory.ts | 67 +++++ .../daemon/persistence_daemon.ts | 137 +++++++++++ src/server/ProcessManager.ts | 60 ----- src/server/daemon/current_daemon_pid.txt | 1 - src/server/daemon/persistence_daemon.ts | 117 --------- .../session_crashes_@ 2019-12-11T08:31:56.281Z.log | 1 - .../session_crashes_@ 2019-12-11T08:43:46.454Z.log | 2 - src/server/index.ts | 11 +- 12 files changed, 220 insertions(+), 461 deletions(-) delete mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log delete mode 100644 logs/server_pids.txt create mode 100644 src/server/ChildProcessUtilities/ProcessFactory.ts create mode 100644 src/server/ChildProcessUtilities/daemon/persistence_daemon.ts delete mode 100644 src/server/ProcessManager.ts delete mode 100644 src/server/daemon/current_daemon_pid.txt delete mode 100644 src/server/daemon/persistence_daemon.ts delete mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log delete mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log (limited to 'src') diff --git a/.gitignore b/.gitignore index cf4ed308b..38c619c52 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ ClientUtils.ts solr-8.1.1/server/ src/server/public/files/ src/scraping/acm/package-lock.json +src/server/ChildProcessUtilities/daemon/**/*.log diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log deleted file mode 100644 index 37e232d48..000000000 --- a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log +++ /dev/null @@ -1,274 +0,0 @@ - -> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web -> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts - -Using ts-node version 7.0.1, typescript version 3.7.2 -objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined. - -starting execution of preliminary functions... -(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect. -completed preliminary functions -. -Starting type checking and linting service... -Using 1 worker with 2048MB memory limit - -running server in development mode - -registering server routes... -all server routes have been successfully registered: -/ -/activity -/buxton -/delete -/deleteAll -/deleteWithAux -/deleteWithGoogleCredentials -/doc/:docId -/downloadId/:docId -/environment/:key -/getCurrentUser -/getUserDocumentId -/getUsers -/googleDocs/:sector/:action -/googlePhotosMediaDownload -/googlePhotosMediaUpload -/home -/imageHierarchyExport/:docId -/inspectImage -/persist -/pull -/readGoogleAccessToken -/search -/serializeDoc/:docId -/serverHeartbeat -/shutdown -/solr/:action -/textsearch -/thumbnail/:filename -/upload -/uploadDoc -/uploadURI -/version -/writeGoogleAccessToken - -websocket listening on port 4321 -server listening on port 1050 - -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 47419ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 47419ms -Built at: 12/11/2019 3:45:31 AM - Asset Size Chunks  Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes  [emitted] -32f1593298e6e7bee5673bf647328d72.png 429 bytes  [emitted] -718c914a99a2136c01c84e01f63e505a.png 829 bytes  [emitted] -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes  [emitted] - assets/downarrow.png 3.28 KiB  [emitted] - assets/env.json 360 bytes  [emitted] - assets/google_photos.png 114 KiB  [emitted] - assets/google_tags.png 7.9 KiB  [emitted] - assets/loading.gif 112 KiB  [emitted] - assets/pdf.worker.js 1.55 MiB  [emitted] - bundle.js 20.8 MiB bundle [emitted] bundle - bundle.js.map 23.3 MiB bundle [emitted] bundle - debug/repl.html 348 bytes  [emitted] - debug/test.html 245 bytes  [emitted] - debug/viewer.html 357 bytes  [emitted] -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes  [emitted] - imageUpload.js 20.8 MiB imageUpload [emitted] imageUpload - imageUpload.js.map 23.3 MiB imageUpload [emitted] imageUpload - index.html 593 bytes  [emitted] - inkControls.js 116 KiB inkControls [emitted] inkControls - inkControls.js.map 122 KiB inkControls [emitted] inkControls - mobile/image.html 333 bytes  [emitted] - mobile/ink.html 252 bytes  [emitted] - repl.js 9.31 MiB repl [emitted] repl - repl.js.map 10.5 MiB repl [emitted] repl - test.js 1.2 MiB test [emitted] test - test.js.map 1.42 MiB test [emitted] test - test.pdf 53.6 KiB  [emitted] - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - viewer.js 9.47 MiB viewer [emitted] viewer - viewer.js.map 10.7 MiB viewer [emitted] viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} [built] -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} [built] -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} [built] -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} [built] -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} [built] -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} [built] - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} [built] - [./src/client/views/Main.tsx] 4.03 KiB {bundle} [built] - [./src/debug/Repl.tsx] 6.87 KiB {repl} [built] - [./src/debug/Test.tsx] 1.02 KiB {test} [built] - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} [built] - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} [built] - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} [built] - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -ℹ 「wdm」: Compiling... -webpack building... -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 615ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 615ms -Built at: 12/11/2019 3:45:33 AM - Asset Size Chunks Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes   -32f1593298e6e7bee5673bf647328d72.png 429 bytes   -718c914a99a2136c01c84e01f63e505a.png 829 bytes   -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes   - bundle.js 20.8 MiB bundle bundle - bundle.js.map 23.3 MiB bundle bundle -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes   - imageUpload.js 20.8 MiB imageUpload imageUpload - imageUpload.js.map 23.3 MiB imageUpload imageUpload - inkControls.js 116 KiB inkControls inkControls - inkControls.js.map 122 KiB inkControls inkControls - repl.js 9.31 MiB repl repl - repl.js.map 10.5 MiB repl repl - test.js 1.2 MiB test test - test.js.map 1.42 MiB test test - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker vendors~pdfjsWorker - viewer.js 9.47 MiB viewer viewer - viewer.js.map 10.7 MiB viewer viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} - [./src/client/views/Main.tsx] 4.03 KiB {bundle} - [./src/debug/Repl.tsx] 6.87 KiB {repl} - [./src/debug/Test.tsx] 1.02 KiB {test} - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21): -prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25): -prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36): -triple-equals: == should be === -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29): -prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21): -prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13): -prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13): -prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13): -prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21): -prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13): -prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25): -prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13): -prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13): -prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13): -prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17): -prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21): -prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17): -prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25): -prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17): -prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'. -No type errors found -Version: typescript 3.7.2, tslint 5.18.0 -Time: 5473ms diff --git a/logs/server_pids.txt b/logs/server_pids.txt deleted file mode 100644 index 2aa143f24..000000000 --- a/logs/server_pids.txt +++ /dev/null @@ -1 +0,0 @@ -9675 created at Wed, 11 Dec 2019 08:44:28 GMT diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 9bdc4ed93..2e62443c6 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile, exists, mkdir, unlink } from 'fs'; +import { readFile, writeFile, exists, mkdir, unlink, createWriteStream } from 'fs'; import { ExecOptions } from 'shelljs'; import { exec } from 'child_process'; import * as path from 'path'; @@ -10,6 +10,11 @@ export function pathFromRoot(relative: string) { return path.resolve(projectRoot, relative); } +export async function fileDescriptorFromStream(path: string) { + const logStream = createWriteStream(path); + return new Promise(resolve => logStream.on("open", resolve)); +} + export const command_line = (command: string, fromDirectory?: string) => { return new Promise((resolve, reject) => { const options: ExecOptions = {}; @@ -54,7 +59,7 @@ export async function log_execution({ startMessage, endMessage, action, color } catch (e) { error = e; } finally { - log_helper(`${typeof endMessage === "string" ? endMessage : endMessage({ result, error })}.`, resolvedColor); + log_helper(typeof endMessage === "string" ? endMessage : endMessage({ result, error }), resolvedColor); } return result; } diff --git a/src/server/ChildProcessUtilities/ProcessFactory.ts b/src/server/ChildProcessUtilities/ProcessFactory.ts new file mode 100644 index 000000000..745b1479a --- /dev/null +++ b/src/server/ChildProcessUtilities/ProcessFactory.ts @@ -0,0 +1,67 @@ +import { existsSync, mkdirSync } from "fs"; +import { pathFromRoot, log_execution, fileDescriptorFromStream } from '../ActionUtilities'; +import { red, green } from "colors"; +import rimraf = require("rimraf"); +import { ChildProcess, spawn, StdioOptions } from "child_process"; +import { Stream } from "stream"; +import { resolve } from "path"; + +export namespace ProcessFactory { + + export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; + + export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise { + if (stdio === "logfile") { + const log_fd = await Logger.create(command, args); + stdio = ["ignore", log_fd, log_fd]; + } + const child = spawn(command, args, { detached, stdio }); + child.unref(); + return child; + } + + export namespace NamedAgents { + + export async function persistenceDaemon() { + await log_execution({ + startMessage: "\ninitializing persistence daemon", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + if (!success) { + console.log(red("failed to initialize the persistance daemon")); + console.log(error); + process.exit(0); + } + return "failsafe daemon process successfully spawned"; + }, + action: () => createWorker('npx', ['ts-node', resolve(__dirname, "./daemon/persistence_daemon.ts")], ["ignore", "inherit", "inherit"]), + color: green + }); + console.log(); + } + } + +} + +export namespace Logger { + + const logPath = pathFromRoot("./logs"); + + export async function initialize() { + if (existsSync(logPath)) { + if (!process.env.SPAWNED) { + await new Promise(resolve => rimraf(logPath, resolve)); + } + } + mkdirSync(logPath); + } + + export async function create(command: string, args?: readonly string[]): Promise { + return fileDescriptorFromStream(generate_log_path(command, args)); + } + + function generate_log_path(command: string, args?: readonly string[]) { + return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`); + } + +} \ No newline at end of file diff --git a/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts new file mode 100644 index 000000000..888cf38b8 --- /dev/null +++ b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts @@ -0,0 +1,137 @@ +import * as request from "request-promise"; +import { log_execution } from "../../ActionUtilities"; +import { red, yellow, cyan, green, Color } from "colors"; +import * as nodemailer from "nodemailer"; +import { MailOptions } from "nodemailer/lib/json-transport"; +import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; +import { resolve } from 'path'; +import { ChildProcess } from "child_process"; +import { ProcessFactory } from "../ProcessFactory"; + +const identifier = yellow("__daemon__:"); + +process.on('SIGINT', () => current_backup?.kill("SIGTERM")); + +const logPath = resolve(__dirname, "./logs"); +const crashPath = resolve(logPath, "./crashes"); +if (!existsSync(logPath)) { + mkdirSync(logPath); +} +if (!existsSync(crashPath)) { + mkdirSync(crashPath); +} + +const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); +function addLogEntry(message: string, color: Color) { + const formatted = color(`${message} ${timestamp()}.`); + identifiedLog(formatted); + appendFileSync(crashLogPath, `${formatted}\n`); +} + +function identifiedLog(message?: any, ...optionalParams: any[]) { + console.log(identifier, message, ...optionalParams); +} + +const LOCATION = "http://localhost"; +const recipient = "samuel_wilkins@brown.edu"; +const frequency = 10; +const { pid } = process; +let restarting = false; + +identifiedLog("Initializing daemon..."); + +writeLocalPidLog("daemon", pid); + +function writeLocalPidLog(filename: string, contents: any) { + const path = `./logs/current_${filename}_pid.log`; + identifiedLog(cyan(`${contents} written to ${path}`)); + writeFileSync(resolve(__dirname, path), `${contents}\n`); +} + +function timestamp() { + return `@ ${new Date().toISOString()}`; +} + +let current_backup: ChildProcess | undefined = undefined; + +async function listen() { + identifiedLog(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); + if (!LOCATION) { + identifiedLog(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); + process.exit(0); + } + const heartbeat = `${LOCATION}:1050/serverHeartbeat`; + // if this is on our remote server, the server must be run in release mode + // const suffix = LOCATION.includes("localhost") ? "" : "-release"; + setInterval(async () => { + let error: any; + try { + await request.get(heartbeat); + if (restarting) { + addLogEntry("Backup server successfully restarted", green); + } + restarting = false; + } catch (e) { + error = e; + } finally { + if (error) { + if (!restarting) { + restarting = true; + addLogEntry("Detected a server crash", red); + current_backup?.kill(); + await log_execution({ + startMessage: identifier + " Sending crash notification email", + endMessage: ({ error, result }) => { + const success = error === null && result === true; + return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; + }, + action: async () => notify(error || "Hmm, no error to report..."), + color: cyan + }); + current_backup = await log_execution({ + startMessage: identifier + " Initiating server restart", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; + }, + action: () => ProcessFactory.createWorker('npm', ['run', 'start-spawn'], "inherit"), + color: green + }); + writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); + } else { + identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + } + } + } + }, 1000 * 10); +} + +function emailText(error: any) { + return [ + `Hey ${recipient.split("@")[0]},`, + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `Location: ${LOCATION}\nError: ${error}`, + "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." + ].join("\n\n"); +} + +async function notify(error: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject: 'Dash Server Crash', + text: emailText(error) + } as MailOptions; + return new Promise(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + }); +} + +listen(); \ No newline at end of file diff --git a/src/server/ProcessManager.ts b/src/server/ProcessManager.ts deleted file mode 100644 index 671f0a234..000000000 --- a/src/server/ProcessManager.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { existsSync, mkdirSync, createWriteStream } from "fs"; -import { pathFromRoot, log_execution } from './ActionUtilities'; -import { red, green } from "colors"; -import rimraf = require("rimraf"); -import { ChildProcess, spawn } from "child_process"; -import { Stream } from "stream"; - -const daemonPath = pathFromRoot("./src/server/daemon/persistence_daemon.ts"); - -export namespace ProcessManager { - - export async function initialize() { - const logPath = pathFromRoot("./logs"); - if (existsSync(logPath)) { - if (!process.env.SPAWNED) { - await new Promise(resolve => rimraf(logPath, resolve)); - } - } - mkdirSync(logPath); - } - - function generate_log_name(command: string, args?: readonly string[]) { - return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`); - } - - export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; - - export async function spawn_detached(command: string, args?: readonly string[], out?: Sink): Promise { - if (!out) { - const logStream = createWriteStream(generate_log_name(command, args)); - out = await new Promise(resolve => logStream.on("open", resolve)); - } - const child = spawn(command, args, { detached: true, stdio: ["ignore", out, out] }); - child.unref(); - return child; - } - - let daemonInitialized = false; - export async function trySpawnDaemon() { - if (!process.env.SPAWNED && !daemonInitialized) { - daemonInitialized = true; - await log_execution({ - startMessage: "\ninitializing persistence daemon", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - if (!success) { - console.log(red("failed to initialize the persistance daemon")); - console.log(error); - process.exit(0); - } - return "failsafe daemon process successfully spawned"; - }, - action: () => spawn_detached('npx', ['ts-node', daemonPath], process.stdout), - color: green - }); - console.log(); - } - } - -} \ No newline at end of file diff --git a/src/server/daemon/current_daemon_pid.txt b/src/server/daemon/current_daemon_pid.txt deleted file mode 100644 index f3cd0298c..000000000 --- a/src/server/daemon/current_daemon_pid.txt +++ /dev/null @@ -1 +0,0 @@ -9626 \ No newline at end of file diff --git a/src/server/daemon/persistence_daemon.ts b/src/server/daemon/persistence_daemon.ts deleted file mode 100644 index 099c7898c..000000000 --- a/src/server/daemon/persistence_daemon.ts +++ /dev/null @@ -1,117 +0,0 @@ -import * as request from "request-promise"; -import { log_execution, pathFromRoot } from "../ActionUtilities"; -import { red, yellow, cyan, green, Color } from "colors"; -import * as nodemailer from "nodemailer"; -import { MailOptions } from "nodemailer/lib/json-transport"; -import { writeFileSync, appendFileSync, createWriteStream, existsSync } from "fs"; -import { resolve } from 'path'; -import { ChildProcess } from "child_process"; -import { ProcessManager } from "../ProcessManager"; - -console.log(yellow("Initializing daemon...")); - -process.on('SIGINT', () => current_backup?.kill("SIGTERM")); - -const crashLogPath = resolve(__dirname, `./session_crashes_${timestamp()}.log`); -function addLogEntry(message: string, color: Color) { - const formatted = color(`${message} ${timestamp()}.`); - console.log(formatted); - appendFileSync(crashLogPath, `${formatted}\n`); -} - -const LOCATION = "http://localhost"; -const recipient = "samuel_wilkins@brown.edu"; -let restarting = false; - -const frequency = 10; -const { pid } = process; -writeFileSync(resolve(__dirname, "./current_daemon_pid.txt"), pid); -console.log(cyan(`${pid} written to ./current_daemon_pid.txt`)); - -function timestamp() { - return `@ ${new Date().toISOString()}`; -} - -let current_backup: ChildProcess | undefined = undefined; - -async function listen() { - console.log(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); - if (!LOCATION) { - console.log(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); - process.exit(0); - } - const heartbeat = `${LOCATION}:1050/serverHeartbeat`; - // if this is on our remote server, the server must be run in release mode - // const suffix = LOCATION.includes("localhost") ? "" : "-release"; - setInterval(async () => { - let error: any; - try { - await request.get(heartbeat); - if (restarting) { - addLogEntry("Backup server successfully restarted", green); - } - restarting = false; - } catch (e) { - error = e; - } finally { - if (error) { - if (!restarting) { - restarting = true; - addLogEntry("Detected a server crash", red); - current_backup?.kill(); - await log_execution({ - startMessage: "Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return `${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - current_backup = await log_execution({ - startMessage: "Initiating server restart", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - return success ? "Child process spawned.." : `An error occurred while attempting to restart the server:\n${error}`; - }, - action: () => ProcessManager.spawn_detached('npm', ['run', 'start-spawn']), - color: green - }); - writeFileSync(pathFromRoot("./logs/current_server_pid.txt"), `${current_backup?.pid ?? -1} created ${timestamp()}\n`); - } else { - console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); - } - } - } - }, 1000 * 10); -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${LOCATION}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); - }); -} - -listen(); \ No newline at end of file diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log deleted file mode 100644 index 32b7810ea..000000000 --- a/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log +++ /dev/null @@ -1 +0,0 @@ -Detected a server crash @ 2019-12-11T08:32:36.317Z diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log deleted file mode 100644 index ebb6843c2..000000000 --- a/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log +++ /dev/null @@ -1,2 +0,0 @@ -Detected a server crash @ 2019-12-11T08:44:26.494Z. -Backup server successfully restarted @ 2019-12-11T08:45:33.644Z. diff --git a/src/server/index.ts b/src/server/index.ts index 795418b31..bebb9b365 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -23,7 +23,7 @@ import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { yellow, red } from "colors"; import { disconnect } from "../server/Initialization"; -import { ProcessManager } from "./ProcessManager"; +import { ProcessFactory, Logger } from "./ChildProcessUtilities/ProcessFactory"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); @@ -36,7 +36,7 @@ export const ExitHandlers = new Array<() => void>(); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { - await ProcessManager.initialize(); + await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); @@ -121,11 +121,16 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); + let daemonInitialized = false; + const { SPAWNED, RELEASE } = process.env; addSupervisedRoute({ method: Method.GET, subscription: "/persist", onValidation: ({ res }) => { - ProcessManager.trySpawnDaemon(); + if (RELEASE && !SPAWNED && !daemonInitialized) { + daemonInitialized = true; + ProcessFactory.NamedAgents.persistenceDaemon(); + } res.redirect("/home"); } }); -- cgit v1.2.3-70-g09d2 From 9d8845fb64c08729b446f12206aa5ed215228f4e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 17:00:58 -0500 Subject: cleaned up toottipmenu and richtextschema a bit. fixed some problems with text styles. fixed warnings. --- logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log | 274 ---------------- logs/server_pids.txt | 1 - src/client/util/ProsemirrorExampleTransfer.ts | 2 +- src/client/util/RichTextRules.ts | 162 +++++----- src/client/util/RichTextSchema.tsx | 68 ++-- src/client/util/TooltipTextMenu.tsx | 414 +++++------------------- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/EditableView.tsx | 2 +- src/client/views/MainView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 32 +- 12 files changed, 228 insertions(+), 741 deletions(-) delete mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log delete mode 100644 logs/server_pids.txt (limited to 'src') diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log deleted file mode 100644 index 37e232d48..000000000 --- a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log +++ /dev/null @@ -1,274 +0,0 @@ - -> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web -> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts - -Using ts-node version 7.0.1, typescript version 3.7.2 -objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined. - -starting execution of preliminary functions... -(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect. -completed preliminary functions -. -Starting type checking and linting service... -Using 1 worker with 2048MB memory limit - -running server in development mode - -registering server routes... -all server routes have been successfully registered: -/ -/activity -/buxton -/delete -/deleteAll -/deleteWithAux -/deleteWithGoogleCredentials -/doc/:docId -/downloadId/:docId -/environment/:key -/getCurrentUser -/getUserDocumentId -/getUsers -/googleDocs/:sector/:action -/googlePhotosMediaDownload -/googlePhotosMediaUpload -/home -/imageHierarchyExport/:docId -/inspectImage -/persist -/pull -/readGoogleAccessToken -/search -/serializeDoc/:docId -/serverHeartbeat -/shutdown -/solr/:action -/textsearch -/thumbnail/:filename -/upload -/uploadDoc -/uploadURI -/version -/writeGoogleAccessToken - -websocket listening on port 4321 -server listening on port 1050 - -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 47419ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 47419ms -Built at: 12/11/2019 3:45:31 AM - Asset Size Chunks  Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes  [emitted] -32f1593298e6e7bee5673bf647328d72.png 429 bytes  [emitted] -718c914a99a2136c01c84e01f63e505a.png 829 bytes  [emitted] -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes  [emitted] - assets/downarrow.png 3.28 KiB  [emitted] - assets/env.json 360 bytes  [emitted] - assets/google_photos.png 114 KiB  [emitted] - assets/google_tags.png 7.9 KiB  [emitted] - assets/loading.gif 112 KiB  [emitted] - assets/pdf.worker.js 1.55 MiB  [emitted] - bundle.js 20.8 MiB bundle [emitted] bundle - bundle.js.map 23.3 MiB bundle [emitted] bundle - debug/repl.html 348 bytes  [emitted] - debug/test.html 245 bytes  [emitted] - debug/viewer.html 357 bytes  [emitted] -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes  [emitted] - imageUpload.js 20.8 MiB imageUpload [emitted] imageUpload - imageUpload.js.map 23.3 MiB imageUpload [emitted] imageUpload - index.html 593 bytes  [emitted] - inkControls.js 116 KiB inkControls [emitted] inkControls - inkControls.js.map 122 KiB inkControls [emitted] inkControls - mobile/image.html 333 bytes  [emitted] - mobile/ink.html 252 bytes  [emitted] - repl.js 9.31 MiB repl [emitted] repl - repl.js.map 10.5 MiB repl [emitted] repl - test.js 1.2 MiB test [emitted] test - test.js.map 1.42 MiB test [emitted] test - test.pdf 53.6 KiB  [emitted] - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - viewer.js 9.47 MiB viewer [emitted] viewer - viewer.js.map 10.7 MiB viewer [emitted] viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} [built] -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} [built] -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} [built] -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} [built] -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} [built] -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} [built] - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} [built] - [./src/client/views/Main.tsx] 4.03 KiB {bundle} [built] - [./src/debug/Repl.tsx] 6.87 KiB {repl} [built] - [./src/debug/Test.tsx] 1.02 KiB {test} [built] - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} [built] - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} [built] - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} [built] - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -ℹ 「wdm」: Compiling... -webpack building... -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 615ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 615ms -Built at: 12/11/2019 3:45:33 AM - Asset Size Chunks Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes   -32f1593298e6e7bee5673bf647328d72.png 429 bytes   -718c914a99a2136c01c84e01f63e505a.png 829 bytes   -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes   - bundle.js 20.8 MiB bundle bundle - bundle.js.map 23.3 MiB bundle bundle -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes   - imageUpload.js 20.8 MiB imageUpload imageUpload - imageUpload.js.map 23.3 MiB imageUpload imageUpload - inkControls.js 116 KiB inkControls inkControls - inkControls.js.map 122 KiB inkControls inkControls - repl.js 9.31 MiB repl repl - repl.js.map 10.5 MiB repl repl - test.js 1.2 MiB test test - test.js.map 1.42 MiB test test - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker vendors~pdfjsWorker - viewer.js 9.47 MiB viewer viewer - viewer.js.map 10.7 MiB viewer viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} - [./src/client/views/Main.tsx] 4.03 KiB {bundle} - [./src/debug/Repl.tsx] 6.87 KiB {repl} - [./src/debug/Test.tsx] 1.02 KiB {test} - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21): -prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25): -prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36): -triple-equals: == should be === -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29): -prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21): -prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13): -prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13): -prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13): -prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21): -prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13): -prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25): -prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13): -prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13): -prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13): -prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17): -prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21): -prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17): -prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25): -prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17): -prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'. -No type errors found -Version: typescript 3.7.2, tslint 5.18.0 -Time: 5473ms diff --git a/logs/server_pids.txt b/logs/server_pids.txt deleted file mode 100644 index 2aa143f24..000000000 --- a/logs/server_pids.txt +++ /dev/null @@ -1 +0,0 @@ -9675 created at Wed, 11 Dec 2019 08:44:28 GMT diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index f1fa6f11d..3324d8abe 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -105,7 +105,7 @@ export default function buildKeymap>(schema: S, mapKeys?: return true; }); - bind("Mod-s", TooltipTextMenu.insertStar); + bind("Mod-s", TooltipTextMenu.insertSummarizer); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { const ref = state.selection; diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 22b2a8204..364c85165 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -62,8 +62,9 @@ export const inpRules = { } ), + // set the font size using # new InputRule( - new RegExp(/^#([0-9]+)\s$/), + new RegExp(/^%([0-9]+)\s$/), (state, match, start, end) => { const size = Number(match[1]); const ruleProvider = FormattedTextBox.FocusedBox!.props.ruleProvider; @@ -74,131 +75,123 @@ export const inpRules = { } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); }), + + // make current selection a hyperlink portal (assumes % was used to initiate an EnteringStyle mode) + new InputRule( + new RegExp(/@$/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null; + + const value = state.doc.textBetween(start, end); + if (value) { + DocServer.GetRefField(value).then(docx => { + const doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); + DocUtils.Publish(doc, value, returnFalse, returnFalse); + }); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value }); + return state.tr.addMark(start, end, link); + } + return state.tr; + }), + + // activate a style by name using prefix '%' new InputRule( new RegExp(/%[a-z]+$/), (state, match, start, end) => { const color = match[0].substring(1, match[0].length); - let marks = TooltipTextMenuManager.Instance._brushMap.get(color); + const marks = TooltipTextMenuManager.Instance._brushMap.get(color); if (marks) { - let tr = state.tr.deleteRange(start, end); + const tr = state.tr.deleteRange(start, end); return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; } - let isValidColor = (strColor: string) => { - var s = new Option().style; + const isValidColor = (strColor: string) => { + const s = new Option().style; s.color = strColor; - return s.color == strColor.toLowerCase(); // 'false' if color wasn't assigned - } + return s.color === strColor.toLowerCase(); // 'false' if color wasn't assigned + }; if (isValidColor(color)) { return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); } return null; }), + // stop using active style new InputRule( new RegExp(/%%$/), (state, match, start, end) => { - let tr = state.tr.deleteRange(start, end); - let marks = state.tr.selection.$anchor.nodeBefore?.marks; + const tr = state.tr.deleteRange(start, end); + const marks = state.tr.selection.$anchor.nodeBefore?.marks; return marks ? Array.from(marks).filter(m => m !== state.schema.marks.user_mark).reduce((tr, m) => tr.removeStoredMark(m), tr) : tr; }), + + // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) new InputRule( - new RegExp(/t$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; - const node = (state.doc.resolve(start) as any).nodeAfter; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), - new InputRule( - new RegExp(/i$/), + new RegExp(/[ti!x]$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; + if (state.selection.to === state.selection.from || !(schema as any).EnteringStyle) return null; + const tag = match[0] === "t" ? "todo" : match[0] === "i" ? "ignore" : match[0] === "x" ? "disagree" : match[0] === "!" ? "important" : "??"; const node = (state.doc.resolve(start) as any).nodeAfter; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; }), + + // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode) new InputRule( - new RegExp(/d$/), + new RegExp(/(%d|d)$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; + if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null; const pos = (state.doc.resolve(start) as any); - let depth = pos.path.length / 3 - 1; - for (; depth >= 0; depth--) { - let node = pos.node(depth); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); if (node.type === schema.nodes.paragraph) { const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === 25 ? undefined : 25 }); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; } } return null; }), + + // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) new InputRule( - new RegExp(/h$/), + new RegExp(/(%h|h)$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; + if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null; const pos = (state.doc.resolve(start) as any); - let depth = pos.path.length / 3 - 1; - for (; depth >= 0; depth--) { - let node = pos.node(depth); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); if (node.type === schema.nodes.paragraph) { const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, indent: node.attrs.indent === -25 ? undefined : -25 }); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; } } return null; }), + // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) new InputRule( - new RegExp(/q$/), + new RegExp(/(%q|q)$/), (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; + if (!match[0].startsWith("%") && !(schema as any).EnteringStyle) return null; const pos = (state.doc.resolve(start) as any); - if (state.selection instanceof NodeSelection && (state.selection as NodeSelection).node.type === schema.nodes.ordered_list) { - let node = (state.selection as NodeSelection).node; + if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { + const node = state.selection.node; return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); } - let depth = pos.path.length / 3 - 1; - for (; depth >= 0; depth--) { - let node = pos.node(depth); + for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { + const node = pos.node(depth); if (node.type === schema.nodes.paragraph) { const replaced = state.tr.setNodeMarkup(pos.pos - pos.parentOffset - 1, node.type, { ...node.attrs, inset: node.attrs.inset === 30 ? undefined : 30 }); - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + const result = replaced.setSelection(new TextSelection(replaced.doc.resolve(start))); + return match[0].startsWith("%") ? result.deleteRange(start, end) : result; } } return null; }), - new InputRule( - new RegExp(/!$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; - const node = (state.doc.resolve(start) as any).nodeAfter; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), - new InputRule( - new RegExp(/x$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from && !(state as any).EnteringStyle) return null; - const node = (state.doc.resolve(start) as any).nodeAfter; - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; - }), - new InputRule( - new RegExp(/@$/), - (state, match, start, end) => { - if (state.selection.to === state.selection.from) return null; - const value = state.doc.textBetween(start, end); - if (value) { - DocServer.GetRefField(value).then(docx => { - let doc = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); - DocUtils.Publish(doc, value, returnFalse, returnFalse); - }); - const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + value), location: "onRight", title: value }); - return state.tr.addMark(start, end, link); - } - return state.tr; - }), + + // center justify text new InputRule( - new RegExp(/^\^\^\s$/), + new RegExp(/%\^$/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; @@ -212,8 +205,9 @@ export const inpRules = { state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), + // left justify text new InputRule( - new RegExp(/^\[\[\s$/), + new RegExp(/%\[$/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; @@ -227,8 +221,9 @@ export const inpRules = { state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), + // right justify text new InputRule( - new RegExp(/^\]\]\s$/), + new RegExp(/%\]$/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; @@ -243,7 +238,7 @@ export const inpRules = { return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), new InputRule( - new RegExp(/##\s$/), + new RegExp(/%#$/), (state, match, start, end) => { const target = Docs.Create.TextDocument({ width: 75, height: 35, backgroundColor: "yellow", autoHeight: true, fontSize: 9, title: "inline comment" }); const node = (state.doc.resolve(start) as any).nodeAfter; @@ -255,26 +250,25 @@ export const inpRules = { return replaced;//.setSelection(new NodeSelection(replaced.doc.resolve(end))); }), new InputRule( - new RegExp(/\(\(/), + new RegExp(/%\(/), (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks || undefined; - const mark = state.schema.marks.highlight.create(); + const mark = state.schema.marks.summarizeInclusive.create(); const selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); const content = selected.selection.content(); const replaced = node ? selected.replaceRangeWith(start, start, - schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + schema.nodes.summary.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))); }), new InputRule( - new RegExp(/\)\)/), + new RegExp(/%\)/), (state, match, start, end) => { - const mark = state.schema.marks.highlight.create(); - return state.tr.removeStoredMark(mark); + return state.tr.removeStoredMark(state.schema.marks.summarizeInclusive.create()); }), new InputRule( - new RegExp(/\^f\s$/), + new RegExp(/%f\$/), (state, match, start, end) => { const newNode = schema.nodes.footnote.create({}); const tr = state.tr; @@ -283,9 +277,5 @@ export const inpRules = { tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node) tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))); }), - // let newNode = schema.nodes.footnote.create({}); - // if (dispatch && state.selection.from === state.selection.to) { - // return true; - // } ] }; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index f9251fb7e..543f45731 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -109,7 +109,7 @@ export const nodes: { [index: string]: NodeSpec } = { }, }, - star: { + summary: { inline: true, attrs: { visibility: { default: false }, @@ -121,15 +121,6 @@ export const nodes: { [index: string]: NodeSpec } = { const attrs = { style: `width: 40px` }; return ["span", { ...node.attrs, ...attrs }]; }, - // parseDOM: [{ - // tag: "star", getAttrs(dom: any) { - // return { - // visibility: dom.getAttribute("visibility"), - // oldtext: dom.getAttribute("oldtext"), - // oldtextlen: dom.getAttribute("oldtextlen"), - // } - // } - // }] }, // :: NodeSpec An inline image (``) node. Supports `src`, @@ -228,6 +219,7 @@ export const nodes: { [index: string]: NodeSpec } = { mapStyle: { default: "decimal" }, setFontSize: { default: undefined }, setFontFamily: { default: "inherit" }, + setFontColor: { default: "inherit" }, inheritedFontSize: { default: undefined }, visibility: { default: true }, indent: { default: undefined } @@ -237,8 +229,10 @@ export const nodes: { [index: string]: NodeSpec } = { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; const ffam = node.attrs.setFontFamily; - return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; margin-left: ${node.attrs.indent}` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }]; + const color = node.attrs.setFontColor; + return node.attrs.visibility ? + ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] : + ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; } }, @@ -318,7 +312,7 @@ export const marks: { [index: string]: MarkSpec } = { attrs: { highlight: { default: "transparent" } }, - inclusive: false, + inclusive: true, parseDOM: [{ tag: "span", getAttrs(dom: any) { return { highlight: dom.getAttribute("backgroundColor") }; @@ -401,7 +395,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, - highlight: { + summarizeInclusive: { parseDOM: [ { tag: "span", @@ -410,7 +404,7 @@ export const marks: { [index: string]: MarkSpec } = { const style = getComputedStyle(p); if (style.textDecoration === "underline") return null; if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && - p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + p.parentElement.outerHTML.indexOf("text-decoration-style: solid") !== -1) { return null; } } @@ -419,6 +413,31 @@ export const marks: { [index: string]: MarkSpec } = { }, ], inclusive: true, + toDOM() { + return ['span', { + style: 'text-decoration: underline; text-decoration-style: solid; text-decoration-color: rgba(204, 206, 210, 0.92)' + }]; + } + }, + + summarize: { + inclusive: false, + parseDOM: [ + { + tag: "span", + getAttrs: (p: any) => { + if (typeof (p) !== "string") { + const style = getComputedStyle(p); + if (style.textDecoration === "underline") return null; + if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 && + p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) { + return null; + } + } + return false; + } + }, + ], toDOM() { return ['span', { style: 'text-decoration: underline; text-decoration-style: dotted; text-decoration-color: rgba(204, 206, 210, 0.92)' @@ -637,7 +656,7 @@ export class DashDocCommentView { } const dashDoc = view.state.schema.nodes.dashDoc.create({ width: 75, height: 35, title: "dashDoc", docid: node.attrs.docid, float: "right" }); view.dispatch(view.state.tr.insert(getPos() + 1, dashDoc)); - setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))) } catch (e) { } }, 0); + setTimeout(() => { try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + 2))); } catch (e) { } }, 0); return undefined; }; this._collapsed.onpointerdown = (e: any) => { @@ -646,16 +665,16 @@ export class DashDocCommentView { this._collapsed.onpointerup = (e: any) => { const target = targetNode(); if (target) { - let expand = target.hidden; - let tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); + const expand = target.hidden; + const tr = view.state.tr.setNodeMarkup(target.pos, undefined, { ...target.node.attrs, hidden: target.node.attrs.hidden ? false : true }); view.dispatch(tr.setSelection(TextSelection.create(tr.doc, getPos() + (expand ? 2 : 1)))); // update the attrs setTimeout(() => { expand && DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); - try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))) } catch (e) { } + try { view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.tr.doc, getPos() + (expand ? 2 : 1)))); } catch (e) { } }, 0); } e.stopPropagation(); - } + }; this._collapsed.onpointerenter = (e: any) => { DocServer.GetRefField(node.attrs.docid).then(async dashDoc => dashDoc instanceof Doc && Doc.linkFollowHighlight(dashDoc)); e.preventDefault(); @@ -908,7 +927,7 @@ export class FootnoteView { ignoreMutation() { return true; } } -export class SummarizedView { +export class SummaryView { _collapsed: HTMLElement; _view: any; constructor(node: any, view: any, getPos: any) { @@ -946,7 +965,8 @@ export class SummarizedView { className = (visible: boolean) => "formattedTextBox-summarizer" + (visible ? "" : "-collapsed"); updateSummarizedText(start?: any) { - const mark = this._view.state.schema.marks.highlight.create(); + const mtype = this._view.state.schema.marks.summarize; + const mtypeInc = this._view.state.schema.marks.summarizeInclusive; let endPos = start; const visited = new Set(); @@ -954,7 +974,7 @@ export class SummarizedView { let skip = false; this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => { if (node.isLeaf && !visited.has(node) && !skip) { - if (node.marks.find((m: any) => m.type === mark.type)) { + if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) { visited.add(node); endPos = i + node.nodeSize - 1; } @@ -979,7 +999,7 @@ const fromJson = schema.nodeFromJSON; schema.nodeFromJSON = (json: any) => { const node = fromJson(json); - if (json.type === "star") { + if (json.type === schema.marks.summarize.name) { node.attrs.text = Slice.fromJSON(schema, node.attrs.textslice); } return node; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index f29dbf2e4..483ab40a7 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,4 +1,3 @@ -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'; @@ -10,8 +9,6 @@ import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { FieldViewProps } from "../views/nodes/FieldView"; import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; -import { DocumentManager } from "./DocumentManager"; -import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; @@ -20,13 +17,11 @@ import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; -import { Keys } from "../views/search/FilterBox"; 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 static Toolbar: HTMLDivElement | undefined; // editor state properties @@ -48,10 +43,9 @@ export class TooltipTextMenu { // editor button doms private colorDom?: Node; private colorDropdownDom?: Node; - private highlightDom?: Node; - private highlightDropdownDom?: Node; + private highighterDom?: Node; + private highlighterDropdownDom?: Node; private linkEditor?: HTMLDivElement; - private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; @@ -94,7 +88,6 @@ export class TooltipTextMenu { { 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') } ]; // add menu items @@ -123,20 +116,15 @@ export class TooltipTextMenu { if (dom.contains(e.target as Node)) { e.stopPropagation(); command(this.view.state, this.view.dispatch, this.view); - // if (this.view.state.selection.empty) { - // if (dom.style.color === "white") { dom.style.color = "greenyellow"; } - // else { dom.style.color = "white"; } - // } } }); - }); - // 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); + // summarize menu + this.highighterDom = this.createHighlightTool().render(this.view).dom; + this.highlighterDropdownDom = this.createHighlightDropdown().render(this.view).dom; + this.tooltip.appendChild(this.highighterDom); + this.tooltip.appendChild(this.highlighterDropdownDom); // color menu this.colorDom = this.createColorTool().render(this.view).dom; @@ -166,7 +154,7 @@ export class TooltipTextMenu { this.tooltip.appendChild(this._brushDropdownDom); // star - this.tooltip.appendChild(this.createStar().render(this.view).dom); + this.tooltip.appendChild(this.createSummarizer().render(this.view).dom); // list types dropdown this.updateListItemDropdown(":", this.listTypeBtnDom); @@ -289,8 +277,6 @@ export class TooltipTextMenu { // stop moving when mouse button is released: document.onpointerup = null; document.onpointermove = null; - //self.highlightSearchTerms(self.state, ["hello"]); - //FormattedTextBox.Instance.unhighlightSearchTerms(); } } @@ -302,11 +288,10 @@ export class TooltipTextMenu { fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize)); }); - const newfontSizeDom = (new Dropdown(fontSizeBtns, { - label: label, - css: "color:black; min-width: 60px;" - }) as MenuItem).render(this.view).dom; - if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); } + const newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom; + if (this.fontSizeDom) { + this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); + } else { this.tooltip.appendChild(newfontSizeDom); } @@ -321,11 +306,10 @@ export class TooltipTextMenu { fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily)); }); - const newfontStyleDom = (new Dropdown(fontBtns, { - label: label, - css: "color:black; width: 125px;" - }) as MenuItem).render(this.view).dom; - if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); } + const newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom; + if (this.fontStyleDom) { + this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); + } else { this.tooltip.appendChild(newfontStyleDom); } @@ -333,94 +317,16 @@ export class TooltipTextMenu { } updateLinkMenu() { - if (!this.linkEditor || !this.linkText) { - this.linkEditor = document.createElement("div"); - this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkText = document.createElement("div"); - 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(); }; - const 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) => { - const node = this.view.state.selection.$from.nodeAfter; - const link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - const href: string = link.attrs.href; - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const 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.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.id = "link-drag"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { - if (!this.editorProps) return; - const dragData = new DragManager.LinkDragData(this.editorProps.Document); - dragData.dontClearTextBox = true; - // hack to get source context -sy - const docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); - e.stopPropagation(); - const ctrlKey = e.ctrlKey; - DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, - { - handlers: { - dragComplete: action(() => { - if (dragData.linkDocument) { - const linkDoc = dragData.linkDocument; - const proto = Doc.GetProto(linkDoc); - if (proto && docView) { - proto.sourceContext = docView.props.ContainingCollectionDoc; - } - const 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 - } - } - }), - }, - hideSource: false - }); - e.stopPropagation(); - e.preventDefault(); - }; - this.linkEditor.appendChild(this.linkDrag); - this.tooltip.appendChild(this.linkEditor); - } - - const node = this.view.state.selection.$from.nodeAfter; - const 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.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 = "15px"; + this.linkDrag.style.height = "15px"; + this.linkDrag.title = "Click to set link target"; + this.linkDrag.id = "link-btn"; + this.linkEditor.appendChild(this.linkDrag); + this.tooltip.appendChild(this.linkEditor); } async getTextLinkTargetTitle() { @@ -485,9 +391,7 @@ export class TooltipTextMenu { return div; }, enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } + run(p1, p2, p3, event) { event.stopPropagation(); } }); // menu item to update/apply the hyperlink to the selected text @@ -584,24 +488,6 @@ 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() { const markType = schema.marks.link; return new MenuItem({ @@ -655,22 +541,19 @@ export class TooltipTextMenu { //Make a dropdown of all list types const toAdd: MenuItem[] = []; this.listTypeToIcon.forEach((icon, type) => { - toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType)); + toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType)); }); //option to remove the list formatting - toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType)); + toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType)); - listTypeBtn = (new Dropdown(toAdd, { - label: label, - css: "color:black; width: 40px;" - }) as MenuItem).render(this.view).dom; + listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom; //add this new button and return it this.tooltip.appendChild(listTypeBtn); return listTypeBtn; } - createStar() { + createSummarizer() { return new MenuItem({ title: "Summarize", label: "Summarize", @@ -678,31 +561,17 @@ export class TooltipTextMenu { css: "color:white;", class: "menuicon", execEvent: "", - run: (state, dispatch) => { - TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); - } - + run: (state, dispatch) => TooltipTextMenu.insertSummarizer(state, dispatch) }); } - public static insertStar(state: EditorState, dispatch: any) { + public static insertSummarizer(state: EditorState, dispatch: any) { if (state.selection.empty) return false; - const mark = state.schema.marks.highlight.create(); + const mark = state.schema.marks.summarize.create(); const tr = state.tr; tr.addMark(state.selection.from, state.selection.to, mark); const content = tr.selection.content(); - const 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; - } - - public static insertComment(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - const mark = state.schema.marks.highlight.create(); - const tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - const content = tr.selection.content(); - const newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); return true; } @@ -722,7 +591,7 @@ export class TooltipTextMenu { const color = document.createElement("div"); color.className = "buttonColor"; - color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString(); + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlighter.toString(); const wrapper = document.createElement("div"); wrapper.id = "colorPicker"; @@ -730,17 +599,14 @@ export class TooltipTextMenu { wrapper.appendChild(color); return wrapper; }, - run: (state, dispatch) => { - TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch); - } + run: (state, dispatch) => TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, state, dispatch) }); } public static insertHighlight(color: String, state: EditorState, dispatch: any) { if (state.selection.empty) return false; - const highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color }); - dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); + toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch); } createHighlightDropdown() { @@ -773,22 +639,22 @@ export class TooltipTextMenu { colors.forEach(color => { const button = document.createElement("button"); - button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; + button.className = color === TooltipTextMenuManager.Instance.highlighter ? "colorPicker active" : "colorPicker"; if (color) { button.style.backgroundColor = color; button.textContent = color === "transparent" ? "X" : ""; button.onclick = e => { - TooltipTextMenuManager.Instance.highlight = color; + TooltipTextMenuManager.Instance.highlighter = color; - TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch); + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlighter, self.view.state, self.view.dispatch); // update color menu const highlightDom = self.createHighlightTool().render(self.view).dom; const 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; + self.highighterDom && self.tooltip.replaceChild(highlightDom, self.highighterDom); + self.highlighterDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlighterDropdownDom); + self.highighterDom = highlightDom; + self.highlighterDropdownDom = highlightDropdownDom; }; } colorsWrapper.appendChild(button); @@ -832,19 +698,18 @@ export class TooltipTextMenu { wrapper.appendChild(color); return wrapper; }, - run: (state, dispatch) => { - TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); - } + run: (state, dispatch) => this.insertColor(TooltipTextMenuManager.Instance.color, state, dispatch) }); } - public static insertColor(color: String, state: EditorState, dispatch: any) { + public insertColor(color: String, state: EditorState, dispatch: any) { const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color }); if (state.selection.empty) { dispatch(state.tr.addStoredMark(colorMark)); return false; } - dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark)); + this.setMark(colorMark, state, dispatch); + toggleMark(colorMark.type, { color: color })(state, dispatch); } createColorDropdown() { @@ -883,7 +748,7 @@ export class TooltipTextMenu { button.onclick = e => { TooltipTextMenuManager.Instance.color = color; - TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); + self.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); // update color menu const colorDom = self.createColorTool().render(self.view).dom; @@ -903,13 +768,10 @@ export class TooltipTextMenu { return div; }, enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } + run(p1, p2, p3, event) { event.stopPropagation(); } }); - const colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; - return colorDropdown; + return new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; } createBrush(active: boolean = false) { @@ -933,9 +795,7 @@ export class TooltipTextMenu { self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); self._brushDropdownDom = newBrushDropdowndom; }, - active: (state) => { - return true; - } + active: (state) => true }); } @@ -959,8 +819,7 @@ export class TooltipTextMenu { if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); 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, []); + this.setMark(mark, this.view.state, this.view.dispatch); }); } } @@ -995,9 +854,7 @@ export class TooltipTextMenu { class: "button-setting-disabled", css: "", enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } + run(p1, p2, p3, event) { event.stopPropagation(); } }); const self = this; @@ -1056,71 +913,26 @@ export class TooltipTextMenu { }); const hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0; - const brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; - return brushDom; + return new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; } - //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[]) => { - const { $cursor, ranges } = view.state.selection as TextSelection; - const state = view.state; - const 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++) { - const { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, type); - } - for (const i of ranges) { - if (has) { - toggleMark(type)(view.state, view.dispatch, view); - } - } - } - } - }); - if (markType) { - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - const 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)))); + setMark = (mark: Mark, state: EditorState, dispatch: any) => { + if (mark) { + const node = (state.selection as NodeSelection).node; + if (node?.type === schema.nodes.ordered_list) { + let attrs = node.attrs; + if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; + if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; + const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); + dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); + } else { + toggleMark(mark.type, mark.attrs)(state, dispatch); } - else toggleMark(markType)(view.state, view.dispatch, view); } } changeToFontFamily = (mark: Mark, view: EditorView) => { - const { $cursor, ranges } = view.state.selection as TextSelection; - const state = view.state; - const 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++) { - const { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily); - } - for (const i of ranges) { - if (has) { - toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view); - } - } - } - const fontName = mark.attrs.family; if (fontName) { this.updateFontStyleDropdown(fontName); } if (this.editorProps) { @@ -1130,39 +942,10 @@ export class TooltipTextMenu { 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) { - const 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 })]; + this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch); } changeToFontSize = (mark: Mark, view: EditorView) => { - const { $cursor, ranges } = view.state.selection as TextSelection; - const state = view.state; - const 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++) { - const { $from, $to } = ranges[i]; - has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize); - } - for (const i of ranges) { - if (has) { - toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view); - } - } - } - const size = mark.attrs.fontSize; if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } if (this.editorProps) { @@ -1172,18 +955,11 @@ export class TooltipTextMenu { 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) { - const 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 })]; + this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch); } //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined) => { + changeBulletType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new const view = this.view; if (nodeType === schema.nodes.bullet_list) { @@ -1211,46 +987,40 @@ export class TooltipTextMenu { //css is the style you want applied to the button dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark, view: EditorView) => any) { return new MenuItem({ - title: "", + title: "Set Font Family", label: label, execEvent: "", class: "dropdown-item", css: css, enable() { return true; }, - run() { - changeFontFamily(mark, view); - } + run() { 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: "", + title: "Set Font Size", label: label, execEvent: "", class: "dropdown-item", css: css, enable() { return true; }, - run() { - changeFontSize(mark, view); - } + run() { changeFontSize(mark, view); } }); } //makes a button for the drop down FOR NODE TYPES //css is the style you want applied to the button - dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[]) => any) { + dropdownBulletBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[]) => any) { return new MenuItem({ - title: "", + title: "Set Bullet Style", label: label, execEvent: "", class: "dropdown-item", css: css, enable() { return true; }, - run() { - changeToNodeInGroup(nodeType, view, groupNodes); - } + run() { changeToNodeInGroup(nodeType, view, groupNodes); } }); } @@ -1325,14 +1095,7 @@ export class TooltipTextMenu { getMarksInSelection(state: EditorState) { const found = new Set(); const { from, to } = state.selection as TextSelection; - state.doc.nodesBetween(from, to, (node) => { - const marks = node.marks; - if (marks) { - marks.forEach(m => { - found.add(m); - }); - } - }); + state.doc.nodesBetween(from, to, (node) => node.marks?.forEach(m => found.add(m))); return found; } @@ -1362,12 +1125,6 @@ export class TooltipTextMenu { this.reset_mark_doms(); - // Hide the tooltip if the selection is empty - if (state.selection.empty) { - //this.tooltip.style.display = "none"; - //return; - } - // update link dropdown const linkDropdown = await this.createLinkDropdown(); const newLinkDropdowndom = linkDropdown.render(this.view).dom; @@ -1473,7 +1230,6 @@ export class TooltipTextMenu { } const mark = state.schema.mark(mark_type); return ref_node.marks.includes(mark); - return false; }); } else { @@ -1518,19 +1274,19 @@ export class TooltipTextMenu { export class TooltipTextMenuManager { private static _instance: TooltipTextMenuManager; + private _isPinned: boolean = false; 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 _brushMap: Map> = new Map(); public _brushIsEmpty: boolean = true; public color: String = "#000"; - public highlight: String = "transparent"; + public highlighter: String = "transparent"; public activeMenu: TooltipTextMenu | undefined; @@ -1541,11 +1297,7 @@ export class TooltipTextMenuManager { return TooltipTextMenuManager._instance; } - public get isPinned() { - return this._isPinned; - } + public get isPinned() { return this._isPinned; } - public toggleIsPinned() { - this._isPinned = !this._isPinned; - } + public toggleIsPinned() { this._isPinned = !this._isPinned; } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index dd3b740fb..f366a3677 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -85,14 +85,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._accumulatedTitle.startsWith("#") || this._accumulatedTitle.startsWith("=")) { this._titleControlString = this._accumulatedTitle; } else if (this._titleControlString.startsWith("#")) { - let selectionTitleFieldKey = this._titleControlString.substring(1); + const selectionTitleFieldKey = this._titleControlString.substring(1); selectionTitleFieldKey === "title" && (SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._accumulatedTitle.startsWith("-")); selectionTitleFieldKey && SelectionManager.SelectedDocuments().forEach(d => Doc.SetInPlace(d.props.Document, selectionTitleFieldKey, typeof d.props.Document[selectionTitleFieldKey] === "number" ? +this._accumulatedTitle : this._accumulatedTitle, true) ); } } - })) + })); @action titleEntered = (e: any) => { const key = e.keyCode || e.which; diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d0cecf03d..54def38b5 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -120,7 +120,7 @@ export class EditableView extends React.Component { @action setIsFocused = (value: boolean) => { - let wasFocused = this._editing; + const wasFocused = this._editing; this._editing = value; return wasFocused !== this._editing; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 01cd7957c..25b7dc5ec 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -286,7 +286,7 @@ export class MainView extends React.Component { ContainingCollectionDoc={undefined} zoomToScale={emptyFunction} getScale={returnOne} - /> + />; } @computed get dockingContent() { TraceMobx(); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 411040332..15eec37de 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -240,9 +240,9 @@ export class CollectionView extends Touchable { const mainPath = path.extname(images[this._curLightboxImg]); const nextPath = path.extname(images[(this._curLightboxImg + 1) % images.length]); const prevPath = path.extname(images[(this._curLightboxImg + images.length - 1) % images.length]); - let main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath); - let next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath); - let prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath); + const main = images[this._curLightboxImg].replace(mainPath, "_o" + mainPath); + const next = images[(this._curLightboxImg + 1) % images.length].replace(nextPath, "_o" + nextPath); + const prev = images[(this._curLightboxImg + images.length - 1) % images.length].replace(prevPath, "_o" + prevPath); return !this._isLightboxOpen ? (null) : ((Docu else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... { this._titleRef.current?.setIsFocused(false); - let any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); + const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); any.keeplocation = true; any?.focus(); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9318142e2..ec9e0ce5b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -27,7 +27,7 @@ import { DictationManager } from '../../util/DictationManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; +import { DashDocCommentView, FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummaryView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; @@ -822,7 +822,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & dashComment(node, view, getPos) { return new DashDocCommentView(node, view, getPos); }, dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, - star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, + summary(node, view, getPos) { return new SummaryView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, footnote(node, view, getPos) { return new FootnoteView(node, view, getPos); } }, @@ -907,10 +907,10 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.tryUpdateHeight(); // see if we need to preserve the insertion point - let prosediv = this._proseRef?.children?.[0] as any; - let keeplocation = prosediv?.keeplocation; + const prosediv = this._proseRef?.children?.[0] as any; + const keeplocation = prosediv?.keeplocation; prosediv && (prosediv.keeplocation = undefined); - let pos = this._editorView?.state.selection.$from.pos || 1; + const pos = this._editorView?.state.selection.$from.pos || 1; keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); } onPointerWheel = (e: React.WheelEvent): void => { @@ -925,14 +925,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & onClick = (e: React.MouseEvent): void => { if ((this._editorView!.root as any).getSelection().isCollapsed) { // this is a hack to allow the cursor to be placed at the end of a document when the document ends in an inline dash comment. Apparently Chrome on Windows has a bug/feature which breaks this when clicking after the end of the text. - let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) + const pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + const node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) if (pcords && node?.type === this._editorView!.state.schema.nodes.dashComment) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pcords.pos + 2))); e.preventDefault(); } if (!node && this._proseRef) { - let lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div + const lastNode = this._proseRef.children[this._proseRef.children.length - 1].children[this._proseRef.children[this._proseRef.children.length - 1].children.length - 1]; // get the last prosemirror div if (e.clientY > lastNode.getBoundingClientRect().bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, this._editorView!.state.doc.content.size))); } @@ -973,7 +973,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & // } // } - this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false) + this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500); } @@ -984,7 +984,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (pos && this.props.isSelected(true)) { // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined; //const node = this._editorView!.state.doc.nodeAt(pos.pos); - let $pos = this._editorView!.state.doc.resolve(pos.pos); + const $pos = this._editorView!.state.doc.resolve(pos.pos); let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined; if ($pos.node().type === schema.nodes.ordered_list) { for (let off = 1; off < 100; off++) { @@ -998,7 +998,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { if (select) { - let $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); + const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); if (!highlightOnly) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos))); } @@ -1064,14 +1064,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & return; } if (!this._editorView!.state.selection.empty && e.key === "%") { - (this._editorView!.state as any).EnteringStyle = true; + this._editorView!.state.schema.EnteringStyle = true; e.preventDefault(); e.stopPropagation(); return; } - if (this._editorView!.state.selection.empty || !(this._editorView!.state as any).EnteringStyle) { - (this._editorView!.state as any).EnteringStyle = false; + if (this._editorView!.state.selection.empty || !this._editorView!.state.schema.EnteringStyle) { + this._editorView!.state.schema.EnteringStyle = false; } if (e.key === "Escape") { SelectionManager.DeselectAll(); @@ -1080,7 +1080,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark! : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); + const mark = e.key !== " " && this._lastTimedMark ? this._lastTimedMark : schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); this._lastTimedMark = mark; this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); @@ -1100,7 +1100,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & 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 const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); const dh = NumCast(this.layoutDoc.height, 0); - let newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle this.layoutDoc.height = newHeight; this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; -- cgit v1.2.3-70-g09d2 From 142c77b3a5b6b744b7fdb0e915f78de8ed30e6b0 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 17:57:14 -0500 Subject: fixed scroll bar issue with search results --- src/client/views/search/FilterBox.tsx | 9 --------- src/client/views/search/SearchItem.scss | 1 + 2 files changed, 1 insertion(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index 12e1bc265..684f50766 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -62,15 +62,6 @@ export class FilterBox extends React.Component { super(props); FilterBox.Instance = this; } - - componentDidMount = () => { - document.addEventListener("pointerdown", (e) => { - if (!e.defaultPrevented && e.timeStamp !== this._pointerTime) { - SearchBox.Instance.closeSearch(); - } - }); - } - setupAccordion() { $('document').ready(function () { const acc = document.getElementsByClassName('filter-header'); diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss index 9f12994c3..82ff96700 100644 --- a/src/client/views/search/SearchItem.scss +++ b/src/client/views/search/SearchItem.scss @@ -203,6 +203,7 @@ .searchBox-placeholder { min-height: 50px; margin-left: 150px; + width: calc(100% - 150px); text-transform: uppercase; text-align: left; font-weight: bold; -- cgit v1.2.3-70-g09d2 From 4e8605c9e46acd3f3d9080073bdd6e80f049c85a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 18:45:05 -0500 Subject: fixed searching in text boxes --- src/server/Websocket/Websocket.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 76e02122b..e1e157fc4 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -133,6 +133,7 @@ export namespace WebSocket { "pdf": ["_t", "url"], "audio": ["_t", "url"], "web": ["_t", "url"], + "RichTextField": ["_t", value => value.Text], "date": ["_d", value => new Date(value.date).toISOString()], "proxy": ["_i", "fieldId"], "list": ["_l", list => { -- cgit v1.2.3-70-g09d2 From e01499153fdd335493973dc79d80ccc4ae8df95c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Dec 2019 19:43:37 -0500 Subject: first steps toward restructure of session daemon --- package.json | 2 +- .../daemon/persistence_daemon.ts | 137 --------------------- src/server/ChildProcessUtilities/daemon/session.ts | 137 +++++++++++++++++++++ src/server/index.ts | 14 --- 4 files changed, 138 insertions(+), 152 deletions(-) delete mode 100644 src/server/ChildProcessUtilities/daemon/persistence_daemon.ts create mode 100644 src/server/ChildProcessUtilities/daemon/session.ts (limited to 'src') diff --git a/package.json b/package.json index 97563e137..77a521e27 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", - "start-spawn": "cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "session": "npx ts-node ./src/server/ChildProcessUtilities/daemon/session.ts", "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", diff --git a/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts deleted file mode 100644 index 888cf38b8..000000000 --- a/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts +++ /dev/null @@ -1,137 +0,0 @@ -import * as request from "request-promise"; -import { log_execution } from "../../ActionUtilities"; -import { red, yellow, cyan, green, Color } from "colors"; -import * as nodemailer from "nodemailer"; -import { MailOptions } from "nodemailer/lib/json-transport"; -import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; -import { resolve } from 'path'; -import { ChildProcess } from "child_process"; -import { ProcessFactory } from "../ProcessFactory"; - -const identifier = yellow("__daemon__:"); - -process.on('SIGINT', () => current_backup?.kill("SIGTERM")); - -const logPath = resolve(__dirname, "./logs"); -const crashPath = resolve(logPath, "./crashes"); -if (!existsSync(logPath)) { - mkdirSync(logPath); -} -if (!existsSync(crashPath)) { - mkdirSync(crashPath); -} - -const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); -function addLogEntry(message: string, color: Color) { - const formatted = color(`${message} ${timestamp()}.`); - identifiedLog(formatted); - appendFileSync(crashLogPath, `${formatted}\n`); -} - -function identifiedLog(message?: any, ...optionalParams: any[]) { - console.log(identifier, message, ...optionalParams); -} - -const LOCATION = "http://localhost"; -const recipient = "samuel_wilkins@brown.edu"; -const frequency = 10; -const { pid } = process; -let restarting = false; - -identifiedLog("Initializing daemon..."); - -writeLocalPidLog("daemon", pid); - -function writeLocalPidLog(filename: string, contents: any) { - const path = `./logs/current_${filename}_pid.log`; - identifiedLog(cyan(`${contents} written to ${path}`)); - writeFileSync(resolve(__dirname, path), `${contents}\n`); -} - -function timestamp() { - return `@ ${new Date().toISOString()}`; -} - -let current_backup: ChildProcess | undefined = undefined; - -async function listen() { - identifiedLog(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); - if (!LOCATION) { - identifiedLog(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); - process.exit(0); - } - const heartbeat = `${LOCATION}:1050/serverHeartbeat`; - // if this is on our remote server, the server must be run in release mode - // const suffix = LOCATION.includes("localhost") ? "" : "-release"; - setInterval(async () => { - let error: any; - try { - await request.get(heartbeat); - if (restarting) { - addLogEntry("Backup server successfully restarted", green); - } - restarting = false; - } catch (e) { - error = e; - } finally { - if (error) { - if (!restarting) { - restarting = true; - addLogEntry("Detected a server crash", red); - current_backup?.kill(); - await log_execution({ - startMessage: identifier + " Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - current_backup = await log_execution({ - startMessage: identifier + " Initiating server restart", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; - }, - action: () => ProcessFactory.createWorker('npm', ['run', 'start-spawn'], "inherit"), - color: green - }); - writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); - } else { - identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); - } - } - } - }, 1000 * 10); -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${LOCATION}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); - }); -} - -listen(); \ No newline at end of file diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts new file mode 100644 index 000000000..c41657cc9 --- /dev/null +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -0,0 +1,137 @@ +import * as request from "request-promise"; +import { log_execution } from "../../ActionUtilities"; +import { red, yellow, cyan, green, Color } from "colors"; +import * as nodemailer from "nodemailer"; +import { MailOptions } from "nodemailer/lib/json-transport"; +import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; +import { resolve } from 'path'; +import { ChildProcess } from "child_process"; +import { ProcessFactory } from "../ProcessFactory"; + +const identifier = yellow("__daemon__:"); + +process.on('SIGINT', () => current_backup?.kill("SIGTERM")); + +const logPath = resolve(__dirname, "./logs"); +const crashPath = resolve(logPath, "./crashes"); +if (!existsSync(logPath)) { + mkdirSync(logPath); +} +if (!existsSync(crashPath)) { + mkdirSync(crashPath); +} + +const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); +function addLogEntry(message: string, color: Color) { + const formatted = color(`${message} ${timestamp()}.`); + identifiedLog(formatted); + appendFileSync(crashLogPath, `${formatted}\n`); +} + +function identifiedLog(message?: any, ...optionalParams: any[]) { + console.log(identifier, message, ...optionalParams); +} + +const LOCATION = "http://localhost"; +const recipient = "samuel_wilkins@brown.edu"; +const frequency = 10; +const { pid } = process; +let restarting = false; + +identifiedLog("Initializing daemon..."); + +writeLocalPidLog("daemon", pid); + +function writeLocalPidLog(filename: string, contents: any) { + const path = `./logs/current_${filename}_pid.log`; + identifiedLog(cyan(`${contents} written to ${path}`)); + writeFileSync(resolve(__dirname, path), `${contents}\n`); +} + +function timestamp() { + return `@ ${new Date().toISOString()}`; +} + +let current_backup: ChildProcess | undefined = undefined; + +async function listen() { + identifiedLog(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); + if (!LOCATION) { + identifiedLog(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); + process.exit(0); + } + const heartbeat = `${LOCATION}:1050/serverHeartbeat`; + // if this is on our remote server, the server must be run in release mode + // const suffix = LOCATION.includes("localhost") ? "" : "-release"; + setInterval(async () => { + let error: any; + try { + await request.get(heartbeat); + if (restarting) { + addLogEntry("Backup server successfully restarted", green); + } + restarting = false; + } catch (e) { + error = e; + } finally { + if (error) { + if (!restarting) { + restarting = true; + addLogEntry("Detected a server crash", red); + current_backup?.kill(); + await log_execution({ + startMessage: identifier + " Sending crash notification email", + endMessage: ({ error, result }) => { + const success = error === null && result === true; + return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; + }, + action: async () => notify(error || "Hmm, no error to report..."), + color: cyan + }); + current_backup = await log_execution({ + startMessage: identifier + " Initiating server restart", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; + }, + action: () => ProcessFactory.createWorker('npm', ['run', 'start'], "inherit"), + color: green + }); + writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); + } else { + identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + } + } + } + }, 1000 * 10); +} + +function emailText(error: any) { + return [ + `Hey ${recipient.split("@")[0]},`, + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `Location: ${LOCATION}\nError: ${error}`, + "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." + ].join("\n\n"); +} + +async function notify(error: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject: 'Dash Server Crash', + text: emailText(error) + } as MailOptions; + return new Promise(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + }); +} + +listen(); \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index bebb9b365..bc481e579 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -121,20 +121,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); - let daemonInitialized = false; - const { SPAWNED, RELEASE } = process.env; - addSupervisedRoute({ - method: Method.GET, - subscription: "/persist", - onValidation: ({ res }) => { - if (RELEASE && !SPAWNED && !daemonInitialized) { - daemonInitialized = true; - ProcessFactory.NamedAgents.persistenceDaemon(); - } - res.redirect("/home"); - } - }); - logRegistrationOutcome(); // initialize the web socket (bidirectional communication: if a user changes -- cgit v1.2.3-70-g09d2 From ac3fc59b4574719c22d95910bbbf0430be7de220 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 20:28:11 -0500 Subject: got rid of search_match&search_fields. Now have Doc.SearchQuery() and document.searchMatch --- src/client/views/collections/CollectionTreeView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 15 ++++----------- src/client/views/pdf/PDFViewer.tsx | 10 +++++----- src/client/views/search/SearchBox.tsx | 10 +++++++--- src/client/views/search/SearchItem.tsx | 15 +++++++-------- src/new_fields/Doc.ts | 3 +++ 6 files changed, 27 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index a0ddc8884..ec1e7409f 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -401,7 +401,7 @@ class TreeView extends React.Component { style={{ color: this.props.document.isMinimized ? "red" : "black", background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0", - fontWeight: this.props.document.search_string ? "bold" : undefined, + fontWeight: this.props.document.searchMatch ? "bold" : undefined, outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" }} > diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ec9e0ce5b..7d404b28f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -219,7 +219,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - const res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); + const res = terms.filter(t => t).map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); let tr = this._editorView.state.tr; const flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); @@ -550,16 +550,9 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); - this._searchReactionDisposer = reaction(() => { - return StrCast(this.layoutDoc.search_string); - }, searchString => { - if (searchString) { - this.highlightSearchTerms([searchString]); - } - else { - this.unhighlightSearchTerms(); - } - }, { fireImmediately: true }); + this._searchReactionDisposer = reaction(() => this.layoutDoc.searchMatch, + search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), + { fireImmediately: true }); this._rulesReactionDisposer = reaction(() => { const ruleProvider = this.props.ruleProvider; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3aa5d1d2c..b114cd7ed 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -39,7 +39,7 @@ export const pageSchema = createSchema({ rotation: "number", scrollY: "number", scrollHeight: "number", - search_string: "string" + serachMatch: "boolean" }); pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; @@ -129,10 +129,10 @@ export class PDFViewer extends DocAnnotatableComponent this._showWaiting = this._showCover = true); this.props.startupLive && this.setupPdfJsViewer(); - this._searchReactionDisposer = reaction(() => this.Document.search_string, searchString => { - if (searchString) { - this.search(searchString, true); - this._lastSearch = searchString; + this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => { + if (search) { + this.search(Doc.SearchQuery(), true); + this._lastSearch = Doc.SearchQuery(); } else { setTimeout(() => this._lastSearch === "mxytzlaf" && this.search("mxytzlaf", true), 200); // bcz: how do we clear search highlights? diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index c45fbe210..fe21c29c3 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -85,7 +85,11 @@ export class SearchBox extends React.Component { this._maxSearchIndex = 0; } - enter = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.submitSearch(); } }; + enter = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + this.submitSearch(); + } + } public static async convertDataUri(imageUri: string, returnedFilename: string) { try { @@ -307,7 +311,7 @@ export class SearchBox extends React.Component { this.getResults(this._searchString); if (i < this._results.length) result = this._results[i]; if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + const highlights = Array.from([...Array.from(new Set(result[1]).values())]); this._visibleElements[i] = ; this._isSearch[i] = "search"; } @@ -315,7 +319,7 @@ export class SearchBox extends React.Component { else { result = this._results[i]; if (result) { - const highlights = Array.from([...Array.from(new Set(result[1]).values())]).filter(v => v !== "search_string"); + const highlights = Array.from([...Array.from(new Set(result[1]).values())]); this._visibleElements[i] = ; this._isSearch[i] = "search"; } diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 1007102f6..cdb6dcf7c 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -135,12 +135,11 @@ export class SearchItem extends React.Component { @observable _displayDim = 50; componentDidMount() { - this.props.doc.search_string = this.props.query; - this.props.doc.search_fields = this.props.highlighting.join(", "); + Doc.SetSearchQuery(this.props.query); + this.props.doc.searchMatch = true; } componentWillUnmount() { - this.props.doc.search_string = undefined; - this.props.doc.search_fields = undefined; + this.props.doc.searchMatch = undefined; } //@computed @@ -218,10 +217,10 @@ export class SearchItem extends React.Component { pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } nextHighlight = (e: React.PointerEvent) => { - e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); - const sstring = StrCast(this.props.doc.search_string); - this.props.doc.search_string = ""; - setTimeout(() => this.props.doc.search_string = sstring, 0); + e.preventDefault(); + e.button === 0 && SearchBox.Instance.openSearch(e); + this.props.doc.searchMatch = false; + setTimeout(() => this.props.doc.searchMatch = true, 0); } highlightDoc = (e: React.PointerEvent) => { if (this.props.doc.type === DocumentType.LINK) { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6f55775fe..17c1b7e16 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -644,6 +644,7 @@ export namespace Doc { export class DocData { @observable _user_doc: Doc = undefined!; + @observable _searchQuery: string = ""; } // the document containing the view layout information - will be the Document itself unless the Document has @@ -651,6 +652,8 @@ export namespace Doc { 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(); + export function SearchQuery(): string { return manager._searchQuery; } + export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } export function UserDoc(): Doc { return manager._user_doc; } export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } export function IsBrushed(doc: Doc) { -- cgit v1.2.3-70-g09d2 From 239ce0bd5112d87791f80ba2dcaafd32086e5a4e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 20:41:42 -0500 Subject: from last --- src/client/views/nodes/DocumentView.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3620ccb34..11c012252 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -143,6 +143,7 @@ export class DocumentView extends DocComponent(Docu if (e.altKey && (e.key === "†" || e.key === "t") && !(e.nativeEvent as any).StopPropagationForReal) { (e.nativeEvent as any).StopPropagationForReal = true; // e.stopPropagation() doesn't seem to work... e.stopPropagation(); + e.preventDefault(); if (!StrCast(this.Document.showTitle)) this.Document.showTitle = "title"; if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0); else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... -- cgit v1.2.3-70-g09d2 From 00a4fd713bcf12eb621fde78f0bb8fb5a680fa04 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 23:48:00 -0500 Subject: fixed dragging/toggling of floating layer documents --- src/client/util/DragManager.ts | 9 ++--- src/client/views/TemplateMenu.tsx | 41 +++++++++------------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 2 ++ 4 files changed, 25 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index b681387d1..9e6224d97 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -209,6 +209,7 @@ export namespace DragManager { } draggedDocuments: Doc[]; droppedDocuments: Doc[]; + dragDivName?: string; offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; @@ -366,7 +367,7 @@ export namespace DragManager { dragElement.style.color = "black"; dragElement.style.transformOrigin = "0 0"; dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000"; - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragElement.style.transform = `translate(${x + (options?.offsetX || 0)}px, ${y + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`; dragElement.style.width = `${rect.width / scaleX}px`; dragElement.style.height = `${rect.height / scaleY}px`; @@ -408,7 +409,7 @@ export namespace DragManager { } } - eles.map(ele => ele.hidden = hideSource); + eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource)); let lastX = downX; let lastY = downY; @@ -433,13 +434,13 @@ export namespace DragManager { lastX = e.pageX; lastY = e.pageY; dragElements.map((dragElement, i) => (dragElement.style.transform = - `translate(${(xs[i] += moveX) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) + `translate(${(xs[i] += moveX) + (options?.offsetX || 0)}px, ${(ys[i] += moveY) + (options?.offsetY || 0)}px) scale(${scaleXs[i]}, ${scaleYs[i]})`) ); }; const hideDragShowOriginalElements = () => { dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); - eles.map(ele => ele.hidden = false); + eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false)); }; const endDrag = () => { document.removeEventListener("pointermove", moveHandler, true); diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index e6116ca09..8d9dc4cf7 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -63,31 +63,24 @@ export class TemplateMenu extends React.Component { SelectionManager.DeselectAll(); const topDocView = this.props.docs[0]; const topDoc = topDocView.props.Document; - const xf = topDocView.props.ScreenToLocalTransform(); - const ex = e.target.clientLeft; - const ey = e.target.clientTop; + const ex = e.target.getBoundingClientRect().left; + const ey = e.target.getBoundingClientRect().top; + const de = new DragManager.DocumentDragData([topDoc]); + de.dragDivName = topDocView.props.dragDivName; + de.moveDocument = topDocView.props.moveDocument; undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))(); - if (e.target.checked) { - setTimeout(() => { - const newDocView = DocumentManager.Instance.getDocumentView(topDoc); - if (newDocView) { - const de = new DragManager.DocumentDragData([topDoc]); - de.moveDocument = topDocView.props.moveDocument; - const xf = newDocView.ContentDiv!.getBoundingClientRect(); - DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, { - offsetX: (ex - xf.left), offsetY: (ey - xf.top), - handlers: { dragComplete: () => { }, }, - hideSource: false - }); - } - }, 10); - } else if (topDocView.props.ContainingCollectionView) { - const collView = topDocView.props.ContainingCollectionView; - const [sx, sy] = xf.inverse().transformPoint(0, 0); - const [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy); - topDoc.x = x; - topDoc.y = y; - } + setTimeout(() => { + const newDocView = DocumentManager.Instance.getDocumentView(topDoc); + if (newDocView) { + const contentDiv = newDocView.ContentDiv!; + const xf = contentDiv.getBoundingClientRect(); + DragManager.StartDocumentDrag([contentDiv], de, ex, ey, { + offsetX: ex - xf.left, offsetY: ey - xf.top, + handlers: { dragComplete: () => { }, }, + hideSource: true + }); + } + }, 0); } @undoBatch diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index d599de56e..261a88deb 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -104,6 +104,7 @@ export class CollectionFreeFormDocumentView extends DocComponent boolean; removeDocument?: (doc: Doc) => boolean; moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; @@ -130,6 +131,7 @@ export class DocumentView extends DocComponent(Docu dragData.dropAction = dropAction; dragData.moveDocument = this.Document.onDragStart ? undefined : this.props.moveDocument; dragData.applyAsTemplate = applyAsTemplate; + dragData.dragDivName = this.props.dragDivName; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { handlers: { dragComplete: action((emptyFunction)) -- cgit v1.2.3-70-g09d2 From c11e175790a6eb1b1e51157cd14f92480434fff1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Dec 2019 00:43:12 -0500 Subject: fixed makeTemplate --- src/client/util/DropConverter.ts | 4 ++-- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index b2c720d5d..349d5d7e3 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -10,8 +10,8 @@ import { ScriptField } from "../../new_fields/ScriptField"; function makeTemplate(doc: Doc): boolean { const layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; - const layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0]; - const fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, ""); + const layout = StrCast(layoutDoc.layout).match(/fieldKey={'[^"]*'}/)![0]; + const fieldKey = layout.replace("fieldKey={'", "").replace(/'}$/, ""); const docs = DocListCast(layoutDoc[fieldKey]); let any = false; docs.map(d => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 81d8d467b..ddfb52ba9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -723,6 +723,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } componentDidMount() { + super.componentDidMount(); this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; }, action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)), { fireImmediately: true, name: "doLayout" }); -- cgit v1.2.3-70-g09d2 From b55ce381c905e87421bf011f5cd0cf423f858bb8 Mon Sep 17 00:00:00 2001 From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com> Date: Thu, 12 Dec 2019 10:51:47 -0500 Subject: session --- src/server/ChildProcessUtilities/daemon/session.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index c41657cc9..9e082e665 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -5,7 +5,7 @@ import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; -import { ChildProcess } from "child_process"; +import { ChildProcess, exec } from "child_process"; import { ProcessFactory } from "../ProcessFactory"; const identifier = yellow("__daemon__:"); @@ -21,11 +21,11 @@ if (!existsSync(crashPath)) { mkdirSync(crashPath); } -const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); +const crashLogPath = resolve(crashPath, `./session_crashes_${new Date().toISOString()}.log`); function addLogEntry(message: string, color: Color) { const formatted = color(`${message} ${timestamp()}.`); identifiedLog(formatted); - appendFileSync(crashLogPath, `${formatted}\n`); + // appendFileSync(crashLogPath, `${formatted}\n`); } function identifiedLog(message?: any, ...optionalParams: any[]) { @@ -88,13 +88,24 @@ async function listen() { action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - current_backup = await log_execution({ + await log_execution({ startMessage: identifier + " Initiating server restart", endMessage: ({ result, error }) => { const success = error === null && result !== undefined; return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; }, - action: () => ProcessFactory.createWorker('npm', ['run', 'start'], "inherit"), + action: async () => { + return new Promise(resolve => { + exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => { + if (err) { + identifiedLog(err.message); + return; + } + resolve(); + }); + + }); + }, color: green }); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); -- cgit v1.2.3-70-g09d2 From 85e7ca251beefa0b5417c8e6e6d28f9aaa5d886c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 11:05:36 -0500 Subject: killport --- package.json | 3 ++- src/server/ChildProcessUtilities/daemon/session.ts | 25 ++++------------------ src/typings/index.d.ts | 1 + 3 files changed, 7 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/package.json b/package.json index 77a521e27..c58f542fc 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "js-datepicker": "^4.6.6", "jsonwebtoken": "^8.5.0", "jsx-to-string": "^1.4.0", + "kill-port": "^1.6.0", "lodash": "^4.17.15", "mobile-detect": "^1.4.3", "mobx": "^5.15.0", @@ -231,4 +232,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 9e082e665..41eb6976e 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -6,7 +6,7 @@ import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; import { ChildProcess, exec } from "child_process"; -import { ProcessFactory } from "../ProcessFactory"; +import * as killport from "kill-port"; const identifier = yellow("__daemon__:"); @@ -79,6 +79,7 @@ async function listen() { restarting = true; addLogEntry("Detected a server crash", red); current_backup?.kill(); + await killport(1050, 'tcp'); await log_execution({ startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { @@ -88,26 +89,8 @@ async function listen() { action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - await log_execution({ - startMessage: identifier + " Initiating server restart", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; - }, - action: async () => { - return new Promise(resolve => { - exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => { - if (err) { - identifiedLog(err.message); - return; - } - resolve(); - }); - - }); - }, - color: green - }); + identifiedLog(green("Initiating server restart...")); + current_backup = exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => identifiedLog(err?.message || "Previous server process exited.")); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } else { identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 9e8ace962..674278126 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -2,6 +2,7 @@ declare module 'googlephotos'; declare module 'react-image-lightbox-with-rotate'; +declare module 'kill-port'; declare module '@react-pdf/renderer' { import * as React from 'react'; -- cgit v1.2.3-70-g09d2 From 2a3a9c641da29e92ddd321f6c1f6c62ffe3c040d Mon Sep 17 00:00:00 2001 From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com> Date: Thu, 12 Dec 2019 11:40:56 -0500 Subject: functional session --- src/server/ChildProcessUtilities/daemon/session.ts | 39 ++++++++++++---------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 41eb6976e..d6c8ddb64 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -6,7 +6,7 @@ import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; import { ChildProcess, exec } from "child_process"; -import * as killport from "kill-port"; +const killport = require("kill-port"); const identifier = yellow("__daemon__:"); @@ -37,6 +37,7 @@ const recipient = "samuel_wilkins@brown.edu"; const frequency = 10; const { pid } = process; let restarting = false; +let count = 0; identifiedLog("Initializing daemon..."); @@ -68,7 +69,8 @@ async function listen() { try { await request.get(heartbeat); if (restarting) { - addLogEntry("Backup server successfully restarted", green); + addLogEntry("Backup server successfully " + count ? "restarted" : "started", green); + count++; } restarting = false; } catch (e) { @@ -77,23 +79,26 @@ async function listen() { if (error) { if (!restarting) { restarting = true; - addLogEntry("Detected a server crash", red); - current_backup?.kill(); - await killport(1050, 'tcp'); - await log_execution({ - startMessage: identifier + " Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - identifiedLog(green("Initiating server restart...")); + if (count) { + addLogEntry("Detected a server crash", red); + current_backup?.kill("SIGTERM"); + identifiedLog(yellow("Cleaning up previous connections...")); + await killport(1050, 'tcp').catch((error: any) => identifiedLog(red(error))); + await killport(4321, 'tcp').catch((error: any) => identifiedLog(red(error))); + identifiedLog(yellow("Connections cleared.")); + await log_execution({ + startMessage: identifier + " Sending crash notification email", + endMessage: ({ error, result }) => { + const success = error === null && result === true; + return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; + }, + action: async () => notify(error || "Hmm, no error to report..."), + color: cyan + }); + identifiedLog(green("Initiating server restart...")); + } current_backup = exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => identifiedLog(err?.message || "Previous server process exited.")); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); - } else { - identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); } } } -- cgit v1.2.3-70-g09d2 From d8cac4f0c1d425ef3fd263ae8d488ee62232f1d3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 15:00:42 -0500 Subject: platform dependent --- src/server/ActionUtilities.ts | 5 ++++- src/server/ChildProcessUtilities/daemon/session.ts | 23 +++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 2e62443c6..053576a92 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -6,7 +6,10 @@ import * as rimraf from "rimraf"; import { yellow, Color } from 'colors'; const projectRoot = path.resolve(__dirname, "../../"); -export function pathFromRoot(relative: string) { +export function pathFromRoot(relative?: string) { + if (!relative) { + return projectRoot; + } return path.resolve(projectRoot, relative); } diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 41eb6976e..1a3ce61af 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -1,12 +1,12 @@ import * as request from "request-promise"; -import { log_execution } from "../../ActionUtilities"; +import { log_execution, pathFromRoot } from "../../ActionUtilities"; import { red, yellow, cyan, green, Color } from "colors"; import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; -import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; +import { writeFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; import { ChildProcess, exec } from "child_process"; -import * as killport from "kill-port"; +// import * as killport from "kill-port"; const identifier = yellow("__daemon__:"); @@ -32,12 +32,25 @@ function identifiedLog(message?: any, ...optionalParams: any[]) { console.log(identifier, message, ...optionalParams); } +if (!["win32", "darwin"].includes(process.platform)) { + identifiedLog(red("Invalid operating system: this script is supported only on Mac and Windows.")); + process.exit(1); +} + +const onWindows = process.platform === "win32"; const LOCATION = "http://localhost"; const recipient = "samuel_wilkins@brown.edu"; const frequency = 10; const { pid } = process; let restarting = false; +function startCommand() { + if (onWindows) { + return '"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"'; + } + return `osascript -e 'tell app "Terminal"\ndo script "cd ${pathFromRoot()} && npm run start-release"\nend tell'`; +} + identifiedLog("Initializing daemon..."); writeLocalPidLog("daemon", pid); @@ -79,7 +92,7 @@ async function listen() { restarting = true; addLogEntry("Detected a server crash", red); current_backup?.kill(); - await killport(1050, 'tcp'); + // await killport(1050, 'tcp'); await log_execution({ startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { @@ -90,7 +103,7 @@ async function listen() { color: cyan }); identifiedLog(green("Initiating server restart...")); - current_backup = exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => identifiedLog(err?.message || "Previous server process exited.")); + current_backup = exec(startCommand(), err => identifiedLog(err?.message || "Previous server process exited.")); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } else { identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); -- cgit v1.2.3-70-g09d2 From 860c7907268874f1754dd86bc3a984066552db12 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Dec 2019 15:02:22 -0500 Subject: major changes to drag and drop --- src/client/northstar/dash-nodes/HistogramBox.tsx | 12 +- src/client/util/DocumentManager.ts | 4 +- src/client/util/DragManager.ts | 386 ++++++++------------- src/client/util/LinkManager.ts | 6 +- src/client/views/CollectionLinearView.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/DocumentButtonBar.tsx | 37 +- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/MainView.tsx | 2 +- src/client/views/OverlayView.tsx | 2 - src/client/views/TemplateMenu.tsx | 11 +- .../views/collections/CollectionDockingView.tsx | 20 +- .../collections/CollectionMasonryViewFieldRow.tsx | 14 +- .../views/collections/CollectionSchemaCells.tsx | 21 +- .../CollectionSchemaMovableTableHOC.tsx | 33 +- .../views/collections/CollectionSchemaView.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 24 +- .../CollectionStackingViewFieldColumn.tsx | 8 +- src/client/views/collections/CollectionSubView.tsx | 31 +- .../views/collections/CollectionTreeView.tsx | 28 +- src/client/views/collections/CollectionView.tsx | 4 +- .../views/collections/CollectionViewChromes.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 33 +- src/client/views/linking/LinkMenu.scss | 85 ----- src/client/views/linking/LinkMenuGroup.tsx | 28 +- src/client/views/linking/LinkMenuItem.scss | 87 +++++ src/client/views/linking/LinkMenuItem.tsx | 24 +- src/client/views/nodes/ButtonBox.tsx | 9 +- .../views/nodes/ContentFittingDocumentView.tsx | 7 +- src/client/views/nodes/DocuLinkBox.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 33 +- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 14 +- src/client/views/nodes/ImageBox.tsx | 10 +- src/client/views/pdf/PDFViewer.tsx | 12 +- src/client/views/search/SearchBox.tsx | 2 - src/client/views/search/SearchItem.tsx | 10 +- 37 files changed, 446 insertions(+), 571 deletions(-) create mode 100644 src/client/views/linking/LinkMenuItem.scss (limited to 'src') diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 854135648..8fee53fb9 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -46,8 +46,8 @@ export class HistogramBox extends React.Component { @action dropX = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - let h = Cast(de.data.draggedDocuments[0].data, HistogramField); + if (de.complete.docDragData) { + let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField); if (h) { this.HistoOp.X = h.HistoOp.X; } @@ -57,8 +57,8 @@ export class HistogramBox extends React.Component { } @action dropY = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - let h = Cast(de.data.draggedDocuments[0].data, HistogramField); + if (de.complete.docDragData) { + let h = Cast(de.complete.docDragData.draggedDocuments[0].data, HistogramField); if (h) { this.HistoOp.Y = h.HistoOp.X; } @@ -78,10 +78,10 @@ export class HistogramBox extends React.Component { componentDidMount() { if (this._dropXRef.current) { - this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, { handlers: { drop: this.dropX.bind(this) } }); + this._dropXDisposer = DragManager.MakeDropTarget(this._dropXRef.current, this.dropX.bind(this)); } if (this._dropYRef.current) { - this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, { handlers: { drop: this.dropY.bind(this) } }); + this._dropYDisposer = DragManager.MakeDropTarget(this._dropYRef.current, this.dropY.bind(this)); } reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true }); reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges)); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 07247b7e9..6f65e6c68 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -108,7 +108,7 @@ export class DocumentManager { return init && rest; }) ).reduce((pairs, dv) => { - const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document); + const linksList = DocListCast(dv.props.Document.links); pairs.push(...linksList.reduce((pairs, link) => { const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { @@ -176,7 +176,7 @@ export class DocumentManager { } public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { - const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc); + const linkDocs = link ? [link] : DocListCast(doc.links); SelectionManager.DeselectAll(); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9e6224d97..95529311a 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,5 +1,4 @@ -import { action, runInAction } from "mobx"; -import { Doc, Field } from "../../new_fields/Doc"; +import { Doc, Field, DocListCast } from "../../new_fields/Doc"; import { Cast, ScriptCast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; @@ -22,7 +21,7 @@ export function SetupDrag( docFunc: () => Doc | Promise, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, - options?: any, + treeViewId?: string, dontHideOnDrop?: boolean, dragStarted?: () => void ) { @@ -36,7 +35,7 @@ export function SetupDrag( const dragData = new DragManager.DocumentDragData([doc]); dragData.dropAction = dropAction; dragData.moveDocument = moveFunc; - dragData.options = options; + dragData.treeViewId = treeViewId; dragData.dontHideOnDrop = dontHideOnDrop; DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y); dragStarted && dragStarted(); @@ -65,62 +64,9 @@ export function SetupDrag( return onItemDown; } -function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - const document = SelectionManager.SelectedDocuments()[0]; - document && document.props.removeDocument && document.props.removeDocument(doc); - addDocument(doc); - return true; -} - -export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: number, linkDoc: Doc, sourceDoc: Doc) { - const draggeddoc = LinkManager.Instance.getOppositeAnchor(linkDoc, sourceDoc); - if (draggeddoc) { - const moddrag = await Cast(draggeddoc.annotationOn, Doc); - const dragdocs = moddrag ? [moddrag] : [draggeddoc]; - const dragData = new DragManager.DocumentDragData(dragdocs); - dragData.moveDocument = moveLinkedDocument; - DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } -} - -export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc, singleLink?: Doc) { - const srcTarg = sourceDoc.proto; - let draggedDocs: Doc[] = []; - - if (srcTarg) { - const linkDocs = singleLink ? [singleLink] : LinkManager.Instance.getAllRelatedLinks(srcTarg); - if (linkDocs) { - draggedDocs = linkDocs.map(link => { - const opp = LinkManager.Instance.getOppositeAnchor(link, sourceDoc); - if (opp) return opp; - }) as Doc[]; - } - } - if (draggedDocs.length) { - const moddrag: Doc[] = []; - for (const draggedDoc of draggedDocs) { - const doc = await Cast(draggedDoc.annotationOn, Doc); - if (doc) moddrag.push(doc); - } - const dragdocs = moddrag.length ? moddrag : draggedDocs; - const dragData = new DragManager.DocumentDragData(dragdocs); - dragData.moveDocument = moveLinkedDocument; - DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } -} - - export namespace DragManager { + let dragDiv: HTMLDivElement; + export function Root() { const root = document.getElementById("root"); if (!root) { @@ -128,79 +74,45 @@ export namespace DragManager { } return root; } + export let AbortDrag: () => void = emptyFunction; + export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; - let dragDiv: HTMLDivElement; - - export enum DragButtons { - Left = 1, - Right = 2, - Both = Left | Right - } - - interface DragOptions { - handlers: DragHandlers; - - hideSource: boolean | (() => boolean); - - dragHasStarted?: () => void; - - withoutShiftDrag?: boolean; - - finishDrag?: (dropData: { [id: string]: any }) => void; - - offsetX?: number; - + export interface DragDropDisposer { (): void; } + export interface DragOptions { + dragComplete?: (e: DragCompleteEvent) => void; // function to invoke when drag has completed + hideSource?: boolean; // hide source document during drag + offsetX?: number; // offset of top left of source drag visual from cursor offsetY?: number; } - export interface DragDropDisposer { - (): void; - } - - export class DragCompleteEvent { } - - export interface DragHandlers { - dragComplete: (e: DragCompleteEvent) => void; - } - - export interface DropOptions { - handlers: DropHandlers; - } + // event called when the drag operation results in a drop action export class DropEvent { constructor( readonly x: number, readonly y: number, - readonly data: { [id: string]: any }, - readonly mods: string + readonly complete: DragCompleteEvent, + readonly altKey: boolean, + readonly metaKey: boolean, + readonly ctrlKey: boolean ) { } } - export interface DropHandlers { - drop: (e: Event, de: DropEvent) => void; - } - - export function MakeDropTarget( - element: HTMLElement, - options: DropOptions - ): DragDropDisposer { - if ("canDrop" in element.dataset) { - throw new Error( - "Element is already droppable, can't make it droppable again" - ); + // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated + export class DragCompleteEvent { + constructor(aborted: boolean, dragData: { [id: string]: any }) { + this.aborted = aborted; + this.docDragData = dragData instanceof DocumentDragData ? dragData as DocumentDragData : undefined; + this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData as PdfAnnoDragData : undefined; + this.linkDragData = dragData instanceof LinkDragData ? dragData as LinkDragData : undefined; + this.columnDragData = dragData instanceof ColumnDragData ? dragData as ColumnDragData : undefined; } - element.dataset.canDrop = "true"; - const handler = (e: Event) => { - const ce = e as CustomEvent; - options.handlers.drop(e, ce.detail); - }; - element.addEventListener("dashOnDrop", handler); - return () => { - element.removeEventListener("dashOnDrop", handler); - delete element.dataset.canDrop; - }; + aborted: boolean; + docDragData?: DocumentDragData; + annoDragData?: PdfAnnoDragData; + linkDragData?: LinkDragData; + columnDragData?: ColumnDragData; } - export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; export class DocumentDragData { constructor(dragDoc: Doc[]) { this.draggedDocuments = dragDoc; @@ -210,6 +122,8 @@ export namespace DragManager { draggedDocuments: Doc[]; droppedDocuments: Doc[]; dragDivName?: string; + treeViewId?: string; + dontHideOnDrop?: boolean; offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; @@ -217,16 +131,34 @@ export namespace DragManager { moveDocument?: MoveFunction; isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; + } + export class LinkDragData { + constructor(linkSourceDoc: Doc) { + this.linkSourceDocument = linkSourceDoc; + } + droppedDocuments: Doc[] = []; + linkSourceDocument: Doc; + dontClearTextBox?: boolean; + linkDocument?: Doc; [id: string]: any; } - - export class AnnotationDragData { + export class ColumnDragData { + constructor(colKey: SchemaHeaderField) { + this.colKey = colKey; + } + colKey: SchemaHeaderField; + [id: string]: any; + } + // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected. + // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag + export class PdfAnnoDragData { constructor(dragDoc: Doc, annotationDoc: Doc, dropDoc: Doc) { this.dragDocument = dragDoc; this.dropDocument = dropDoc; this.annotationDocument = annotationDoc; this.offset = [0, 0]; } + linkedToDoc?: boolean; targetContext: Doc | undefined; dragDocument: Doc; annotationDocument: Doc; @@ -236,98 +168,103 @@ export namespace DragManager { userDropAction: dropActionType; } - export let StartDragFunctions: (() => void)[] = []; + export function MakeDropTarget( + element: HTMLElement, + dropFunc: (e: Event, de: DropEvent) => void + ): DragDropDisposer { + if ("canDrop" in element.dataset) { + throw new Error( + "Element is already droppable, can't make it droppable again" + ); + } + element.dataset.canDrop = "true"; + const handler = (e: Event) => dropFunc(e, (e as CustomEvent).detail); + element.addEventListener("dashOnDrop", handler); + return () => { + element.removeEventListener("dashOnDrop", handler); + delete element.dataset.canDrop; + }; + } + // drag a document and drop it (or make an alias/copy on drop) export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { - runInAction(() => StartDragFunctions.map(func => func())); + const finishDrag = (e: DragCompleteEvent) => { + e.docDragData && (e.docDragData.droppedDocuments = + 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) + ); + e.docDragData?.droppedDocuments.forEach((drop: Doc, i: number) => + Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined)); + }; 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 = - 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) - ); - dropData.droppedDocuments.forEach((drop: Doc, i: number) => - Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []).map(prop => drop[prop] = undefined)); - }); + StartDrag(eles, dragData, downX, downY, options, finishDrag); } + // drag a button template and drop a new button export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { - const dragData = new DragManager.DocumentDragData([]); - runInAction(() => StartDragFunctions.map(func => func())); - StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : - (dropData: { [id: string]: any }) => { - const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title }); - bd.onClick = ScriptField.MakeScript(script); - params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); - initialize && initialize(bd); - bd.buttonParams = new List(params); - dropData.droppedDocuments = [bd]; - }); + const finishDrag = (e: DragCompleteEvent) => { + const bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title }); + bd.onClick = ScriptField.MakeScript(script); + params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc))); + initialize && initialize(bd); + bd.buttonParams = new List(params); + e.docDragData && (e.docDragData.droppedDocuments = [bd]); + }; + StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag); } - export function StartLinkedDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { - dragData.moveDocument = moveLinkedDocument; + // drag links and drop link targets (aliasing them if needed) + export async function StartLinkTargetsDrag(dragEle: HTMLElement, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) { + let draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; - runInAction(() => StartDragFunctions.map(func => func())); - StartDrag(eles, dragData, downX, downY, options, - (dropData: { [id: string]: any }) => { - const droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => { - const dvs = DocumentManager.Instance.getDocumentViews(d); - if (dvs.length) { - const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined; - const inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView); - if (inContext.length) { - inContext.forEach(dv => droppedDocs.push(dv.props.Document)); + if (draggedDocs.length) { + const moddrag: Doc[] = []; + for (const draggedDoc of draggedDocs) { + const doc = await Cast(draggedDoc.annotationOn, Doc); + if (doc) moddrag.push(doc); + } + + let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); + dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => { + const document = SelectionManager.SelectedDocuments()[0]; + document && document.props.removeDocument && document.props.removeDocument(doc); + addDocument(doc); + return true; + }; + const containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined; + const finishDrag = (e: DragCompleteEvent) => + e.docDragData && (e.docDragData.droppedDocuments = + dragData.draggedDocuments.reduce((droppedDocs, d) => { + const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView); + if (dvs.length) { + dvs.forEach(dv => droppedDocs.push(dv.props.Document)); } else { droppedDocs.push(Doc.MakeAlias(d)); } - } else { - droppedDocs.push(Doc.MakeAlias(d)); - } - return droppedDocs; - }, []); - dropData.droppedDocuments = droppedDocuments; - }); - } - - export function StartAnnotationDrag(eles: HTMLElement[], dragData: AnnotationDragData, downX: number, downY: number, options?: DragOptions) { - StartDrag(eles, dragData, downX, downY, options); - } + return droppedDocs; + }, [] as Doc[])); - export class LinkDragData { - constructor(linkSourceDoc: Doc, blacklist: Doc[] = []) { - this.linkSourceDocument = linkSourceDoc; - this.blacklist = blacklist; + StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag); } - droppedDocuments: Doc[] = []; - linkSourceDocument: Doc; - blacklist: Doc[]; - dontClearTextBox?: boolean; - [id: string]: any; } - // for column dragging in schema view - export class ColumnDragData { - constructor(colKey: SchemaHeaderField) { - this.colKey = colKey; - } - colKey: SchemaHeaderField; - [id: string]: any; + // drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption + export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) { + StartDrag(eles, dragData, downX, downY, options); } - export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) { - StartDrag([ele], dragData, downX, downY, options); + // drags a linker button and creates a link on drop + export function StartLinkDrag(ele: HTMLElement, sourceDoc: Doc, downX: number, downY: number, options?: DragOptions) { + StartDrag([ele], new DragManager.LinkDragData(sourceDoc), downX, downY, options); } + // drags a column from a schema view export function StartColumnDrag(ele: HTMLElement, dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) { StartDrag([ele], dragData, downX, downY, options); } - export let AbortDrag: () => void = emptyFunction; - - function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { + function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { eles = eles.filter(e => e); if (!dragDiv) { dragDiv = document.createElement("div"); @@ -341,33 +278,29 @@ export namespace DragManager { const xs: number[] = []; const ys: number[] = []; - const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : - dragData instanceof AnnotationDragData ? [dragData.dragDocument] : []; + const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : []; const dragElements = eles.map(ele => { - const w = ele.offsetWidth, - h = ele.offsetHeight; + if (!ele.parentNode) dragDiv.appendChild(ele); + const dragElement = ele.parentNode === dragDiv ? ele : ele.cloneNode(true) as HTMLElement; const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / w, - scaleY = rect.height / h; - const x = rect.left, - y = rect.top; - xs.push(x); - ys.push(y); + const scaleX = rect.width / ele.offsetWidth, + scaleY = rect.height / ele.offsetHeight; + xs.push(rect.left); + ys.push(rect.top); scaleXs.push(scaleX); scaleYs.push(scaleY); - const dragElement = ele.cloneNode(true) as HTMLElement; dragElement.style.opacity = "0.7"; - dragElement.style.borderRadius = getComputedStyle(ele).borderRadius; dragElement.style.position = "absolute"; dragElement.style.margin = "0"; dragElement.style.top = "0"; dragElement.style.bottom = ""; dragElement.style.left = "0"; - dragElement.style.transition = "none"; dragElement.style.color = "black"; + dragElement.style.transition = "none"; dragElement.style.transformOrigin = "0 0"; + dragElement.style.borderRadius = getComputedStyle(ele).borderRadius; dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000"; - dragElement.style.transform = `translate(${x + (options?.offsetX || 0)}px, ${y + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`; + dragElement.style.transform = `translate(${rect.left + (options?.offsetX || 0)}px, ${rect.top + (options?.offsetY || 0)}px) scale(${scaleX}, ${scaleY})`; dragElement.style.width = `${rect.width / scaleX}px`; dragElement.style.height = `${rect.height / scaleY}px`; @@ -385,30 +318,18 @@ export namespace DragManager { Array.from(pdfView).map((v, i) => v.scrollTo({ top: tops[i] })); }, 0); } - const set = dragElement.getElementsByTagName('*'); if (dragElement.hasAttribute("style")) (dragElement as any).style.pointerEvents = "none"; + const set = dragElement.getElementsByTagName('*'); // tslint:disable-next-line: prefer-for-of for (let i = 0; i < set.length; i++) { - if (set[i].hasAttribute("style")) { - const s = set[i]; - (s as any).style.pointerEvents = "none"; - } + set[i].hasAttribute("style") && ((set[i] as any).style.pointerEvents = "none"); } - dragDiv.appendChild(dragElement); return dragElement; }); - let hideSource = false; - if (options) { - if (typeof options.hideSource === "boolean") { - hideSource = options.hideSource; - } else { - hideSource = options.hideSource(); - } - } - + const hideSource = options?.hideSource ? true : false; eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = hideSource) : (ele.hidden = hideSource)); let lastX = downX; @@ -418,9 +339,9 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey ? "alias" : undefined; } - if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { + if (e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); - finishDrag && finishDrag(dragData); + finishDrag?.(new DragCompleteEvent(true, dragData)); CollectionDockingView.Instance.StartOtherDrag({ pageX: e.pageX, pageY: e.pageY, @@ -445,45 +366,40 @@ export namespace DragManager { const endDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - if (options) { - options.handlers.dragComplete({}); - } }; AbortDrag = () => { hideDragShowOriginalElements(); SelectionManager.SetIsDragging(false); + options?.dragComplete?.(new DragCompleteEvent(true, dragData)); endDrag(); }; const upHandler = (e: PointerEvent) => { hideDragShowOriginalElements(); dispatchDrag(eles, e, dragData, options, finishDrag); SelectionManager.SetIsDragging(false); + options?.dragComplete?.(new DragCompleteEvent(false, dragData)); endDrag(); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } - function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { + function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) { const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => { - // let parent = dragEle.parentElement; - // if (parent) parent.removeChild(dragEle); - const ret = [dragEle, dragEle.style.width, dragEle.style.height]; + const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height }; dragEle.style.width = "0"; dragEle.style.height = "0"; return ret; }); const target = document.elementFromPoint(e.x, e.y); removed.map(r => { - const dragEle = r[0] as HTMLElement; - dragEle.style.width = r[1] as string; - dragEle.style.height = r[2] as string; - // let parent = r[1]; - // if (parent && dragEle) parent.appendChild(dragEle); + r.ele.style.width = r.w; + r.ele.style.height = r.h; }); if (target) { - finishDrag && finishDrag(dragData); + const complete = new DragCompleteEvent(false, dragData); + finishDrag?.(complete); target.dispatchEvent( new CustomEvent("dashOnDrop", { @@ -491,8 +407,10 @@ export namespace DragManager { detail: { x: e.x, y: e.y, - data: dragData, - mods: e.altKey ? "AltKey" : e.ctrlKey ? "CtrlKey" : e.metaKey ? "MetaKey" : "" + complete: complete, + altKey: e.altKey, + metaKey: e.metaKey, + ctrlKey: e.ctrlKey } }) ); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index fb6f27478..5f3667acc 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -70,7 +70,7 @@ export class LinkManager { } // finds all links that contain the given anchor - public getAllRelatedLinks(anchor: Doc): Doc[] {//List { + public getAllRelatedLinks(anchor: Doc): Doc[] { const related = LinkManager.Instance.getAllLinks().filter(link => { const protomatch1 = Doc.AreProtosEqual(anchor, Cast(link.anchor1, Doc, null)); const protomatch2 = Doc.AreProtosEqual(anchor, Cast(link.anchor2, Doc, null)); @@ -244,7 +244,5 @@ export class LinkManager { if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc; } } -Scripting.addGlobal(function links(doc: any) { - return new List(LinkManager.Instance.getAllRelatedLinks(doc)); -}); +Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }); \ No newline at end of file diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 293a4feea..5ca861f71 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -39,7 +39,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 27ee9f122..c7ddee7ea 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -71,7 +71,7 @@ export function DocAnnotatableComponent

(schema // if the moved document is already in this overlay collection nothing needs to be done. // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. @action.bound - moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean { return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false; } @action.bound diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 7f125dd34..f014c1329 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -3,13 +3,12 @@ import { faArrowAltCircleDown, faArrowAltCircleUp, faCheckCircle, faCloudUploadA import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, observable, runInAction, computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, DocListCast } from "../../new_fields/Doc"; import { RichTextField } from '../../new_fields/RichTextField'; -import { NumCast, StrCast } from "../../new_fields/Types"; +import { NumCast, StrCast, Cast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { DragManager } from "../util/DragManager"; -import { LinkManager } from '../util/LinkManager'; import { UndoManager } from "../util/UndoManager"; import './DocumentButtonBar.scss'; import './collections/ParentDocumentSelector.scss'; @@ -107,27 +106,21 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], if (this._linkButton.current !== null && (Math.abs(e.clientX - this._downX) > 3 || Math.abs(e.clientY - this._downY) > 3)) { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); - const docView = this.props.views[0]; - const container = docView.props.ContainingCollectionDoc?.proto; - const dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []); const linkDrag = UndoManager.StartBatch("Drag Link"); - DragManager.StartLinkDrag(this._linkButton.current, dragData, e.pageX, e.pageY, { - handlers: { - dragComplete: () => { - const tooltipmenu = FormattedTextBox.ToolTipTextMenu; - const linkDoc = dragData.linkDocument; - if (linkDoc && tooltipmenu) { - const proto = Doc.GetProto(linkDoc); - if (proto && docView) { - proto.sourceContext = docView.props.ContainingCollectionDoc; - } - const text = tooltipmenu.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), e.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 - } + DragManager.StartLinkDrag(this._linkButton.current, this.props.views[0].props.Document, e.pageX, e.pageY, { + dragComplete: dropEv => { + const linkDoc = dropEv.linkDragData?.linkDocument; // equivalent to !dropEve.aborted since linkDocument is only assigned on a completed drop + if (linkDoc && FormattedTextBox.ToolTipTextMenu) { + const proto = Doc.GetProto(linkDoc); + proto.sourceContext = this.props.views[0].props.ContainingCollectionDoc; + + const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast((linkDoc.anchor2 as Doc).title) : "-untitled-"; + const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc, anchor2Title, e.ctrlKey ? "onRight" : "inTab"); + if (linkDoc.anchor2 instanceof Doc) { + proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link } - linkDrag && linkDrag.end(); } + linkDrag?.end(); }, hideSource: false }); @@ -200,7 +193,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], @computed get linkButton() { - const linkCount = LinkManager.Instance.getAllRelatedLinks(this.props.views[0].props.Document).length; + const linkCount = DocListCast(this.props.views[0].props.Document.links).length; return

}> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index f366a3677..fdaca87a9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -199,7 +199,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointermove", this.onTitleMove); document.removeEventListener("pointerup", this.onTitleUp); DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(documentView => documentView.ContentDiv!), dragData, e.x, e.y, { - handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, + dragComplete: action(e => this._hidden = this.Interacting = false), hideSource: true }); e.stopPropagation(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 25b7dc5ec..db2a3c298 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -460,7 +460,7 @@ export class MainView extends React.Component { addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); - moveButtonDoc = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); + moveButtonDoc = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index cd330d492..350a75d29 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -9,8 +9,6 @@ import { DocListCast, Doc } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; import { DocumentView } from "./nodes/DocumentView"; 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"; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 8d9dc4cf7..f7cfc4022 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -66,7 +66,7 @@ export class TemplateMenu extends React.Component { const ex = e.target.getBoundingClientRect().left; const ey = e.target.getBoundingClientRect().top; const de = new DragManager.DocumentDragData([topDoc]); - de.dragDivName = topDocView.props.dragDivName; + de.dragDivName = topDocView.props.dragDivName; de.moveDocument = topDocView.props.moveDocument; undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))(); setTimeout(() => { @@ -74,11 +74,7 @@ export class TemplateMenu extends React.Component { if (newDocView) { const contentDiv = newDocView.ContentDiv!; const xf = contentDiv.getBoundingClientRect(); - DragManager.StartDocumentDrag([contentDiv], de, ex, ey, { - offsetX: ex - xf.left, offsetY: ey - xf.top, - handlers: { dragComplete: () => { }, }, - hideSource: true - }); + DragManager.StartDocumentDrag([contentDiv], de, ex, ey, { offsetX: ex - xf.left, offsetY: ey - xf.top, hideSource: true }); } }, 0); } @@ -148,9 +144,6 @@ export class TemplateMenu extends React.Component { DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { offsetX: dragData.offset[0], offsetY: dragData.offset[1], - handlers: { - dragComplete: action(emptyFunction), - }, hideSource: false }); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index ffcb3e9ce..08b9fd216 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -20,7 +20,7 @@ import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, U import { DocServer } from "../../DocServer"; import { Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; -import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; +import { DragManager } from "../../util/DragManager"; import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from "../../util/UndoManager"; @@ -346,7 +346,7 @@ export class CollectionDockingView extends React.Component) => - (sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc))); + (sourceDoc instanceof Doc) && DragManager.StartLinkTargetsDrag(tab, x, y, sourceDoc))); } if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; @@ -419,15 +419,13 @@ export class CollectionDockingView extends React.Component { - e.preventDefault(); - e.stopPropagation(); - DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { - handlers: { dragComplete: emptyFunction }, - hideSource: false - }); - }}>, dragSpan); + onPointerDown={e => { + e.preventDefault(); + e.stopPropagation(); + DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); + }}> + + , dragSpan); ReactDOM.render(, gearSpan); tab.reactComponents = [dragSpan, gearSpan, upDiv]; tab.element.append(dragSpan); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index d697e721b..80752303c 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -56,7 +56,7 @@ export class CollectionMasonryViewFieldRow extends React.Component { this._dropDisposer && this._dropDisposer(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } }); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } } @@ -74,12 +74,12 @@ export class CollectionMasonryViewFieldRow extends React.Component { this._createAliasSelected = false; - if (de.data instanceof DragManager.DocumentDragData) { + if (de.complete.docDragData) { (this.props.parent.Document.dropConverter instanceof ScriptField) && - this.props.parent.Document.dropConverter.script.run({ dragData: de.data }); + this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData }); const key = StrCast(this.props.parent.props.Document.sectionFilter); const castedValue = this.getValue(this._heading); - de.data.droppedDocuments.forEach(d => d[key] = castedValue); + de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue); this.props.parent.drop(e, de); e.stopPropagation(); } @@ -171,10 +171,8 @@ export class CollectionMasonryViewFieldRow extends React.Component boolean; pinToPres: (document: Doc) => void; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; isFocused: boolean; changeFocusedCellByIndex: (row: number, col: number) => void; setIsEditing: (isEditing: boolean) => void; @@ -105,13 +105,13 @@ export class CollectionSchemaCell extends React.Component { } private drop = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { + if (de.complete.docDragData) { const fieldKey = this.props.rowProps.column.id as string; - if (de.data.draggedDocuments.length === 1) { - this._document[fieldKey] = de.data.draggedDocuments[0]; + if (de.complete.docDragData.draggedDocuments.length === 1) { + this._document[fieldKey] = de.complete.docDragData.draggedDocuments[0]; } else { - const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.data.draggedDocuments, {}); + const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {}); this._document[fieldKey] = coll; } e.stopPropagation(); @@ -121,7 +121,7 @@ export class CollectionSchemaCell extends React.Component { private dropRef = (ele: HTMLElement | null) => { this._dropDisposer && this._dropDisposer(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } @@ -167,11 +167,10 @@ export class CollectionSchemaCell extends React.Component { const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); const onItemDown = (e: React.PointerEvent) => { - if (fieldIsDoc) { - SetupDrag(this._focusRef, () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, - this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); - } + fieldIsDoc && SetupDrag(this._focusRef, + () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, + this._document[props.fieldKey] instanceof Doc ? (doc: Doc, target: Doc | undefined, addDoc: (newDoc: Doc) => any) => addDoc(doc) : this.props.moveDocument, + this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); }; const onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) { diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index 90320df82..153bbd410 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -56,7 +56,7 @@ export class MovableColumn extends React.Component { createColDropTarget = (ele: HTMLDivElement) => { this._colDropDisposer && this._colDropDisposer(); if (ele) { - this._colDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.colDrop.bind(this) } }); + this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this)); } } @@ -66,8 +66,8 @@ export class MovableColumn extends React.Component { const rect = this._header!.current!.getBoundingClientRect(); const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top); const before = x[0] < bounds[0]; - if (de.data instanceof DragManager.ColumnDragData) { - this.props.reorderColumns(de.data.colKey, this.props.columnValue, before, this.props.allColumns); + if (de.complete.columnDragData) { + this.props.reorderColumns(de.complete.columnDragData.colKey, this.props.columnValue, before, this.props.allColumns); return true; } return false; @@ -165,7 +165,7 @@ export class MovableRow extends React.Component { createRowDropTarget = (ele: HTMLDivElement) => { this._rowDropDisposer && this._rowDropDisposer(); if (ele) { - this._rowDropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } }); + this._rowDropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } } @@ -178,16 +178,17 @@ export class MovableRow extends React.Component { const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); const before = x[1] < bounds[1]; - if (de.data instanceof DragManager.DocumentDragData) { + const docDragData = de.complete.docDragData; + if (docDragData) { e.stopPropagation(); - if (de.data.draggedDocuments[0] === rowDoc) return true; + if (docDragData.draggedDocuments[0] === rowDoc) return true; const addDocument = (doc: Doc) => this.props.addDoc(doc, rowDoc, before); - const movedDocs = de.data.draggedDocuments; - return (de.data.dropAction || de.data.userDropAction) ? - de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) - : (de.data.moveDocument) ? - movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, rowDoc, addDocument) || added, false) - : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); + const movedDocs = docDragData.draggedDocuments; + return (docDragData.dropAction || docDragData.userDropAction) ? + docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before) || added, false) + : (docDragData.moveDocument) ? + movedDocs.reduce((added: boolean, d) => docDragData.moveDocument?.(d, rowDoc, addDocument) || added, false) + : docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDoc(d, rowDoc, before), false); } return false; } @@ -199,12 +200,12 @@ export class MovableRow extends React.Component { @undoBatch @action - move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => { - const targetView = DocumentManager.Instance.getDocumentView(target); + move: DragManager.MoveFunction = (doc: Doc, targetCollection: Doc | undefined, addDoc) => { + const targetView = targetCollection && DocumentManager.Instance.getDocumentView(targetCollection); if (targetView && targetView.props.ContainingCollectionDoc) { - return doc !== target && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); + return doc !== targetCollection && doc !== targetView.props.ContainingCollectionDoc && this.props.removeDoc(doc) && addDoc(doc); } - return doc !== target && this.props.removeDoc(doc) && addDoc(doc); + return doc !== targetCollection && this.props.removeDoc(doc) && addDoc(doc); } render() { diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 8169e9e2b..bb706e528 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -224,7 +224,7 @@ export interface SchemaTableProps { renderDepth: number; deleteDocument: (document: Doc) => boolean; addDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; active: (outsideReaction: boolean) => boolean; onDrop: (e: React.DragEvent, options: DocumentOptions, completed?: (() => void) | undefined) => void; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ff3417b77..6a23920f3 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -147,7 +147,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } @action - moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => { + moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean): boolean => { return this.props.removeDocument(doc) && addDocument(doc); } createRef = (ele: HTMLDivElement | null) => { @@ -243,7 +243,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const where = [de.x, de.y]; let targInd = -1; let plusOne = false; - if (de.data instanceof DragManager.DocumentDragData) { + if (de.complete.docDragData) { this._docXfs.map((cd, i) => { const pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); const pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height()); @@ -252,16 +252,16 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { plusOne = (where[1] > (pos[1] + pos1[1]) / 2 ? 1 : 0) ? true : false; } }); - } - if (super.drop(e, de)) { - const newDoc = de.data.droppedDocuments[0]; - const docs = this.childDocList; - if (docs) { - if (targInd === -1) targInd = docs.length; - else targInd = docs.indexOf(this.filteredChildren[targInd]); - const srcInd = docs.indexOf(newDoc); - docs.splice(srcInd, 1); - docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc); + if (super.drop(e, de)) { + const newDoc = de.complete.docDragData.droppedDocuments[0]; + const docs = this.childDocList; + if (docs) { + if (targInd === -1) targInd = docs.length; + else targInd = docs.indexOf(this.filteredChildren[targInd]); + const srcInd = docs.indexOf(newDoc); + docs.splice(srcInd, 1); + docs.splice((targInd > srcInd ? targInd - 1 : targInd) + (plusOne ? 1 : 0), 0, newDoc); + } } } return false; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index ca3b93bf8..39b4e4e1d 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -51,21 +51,21 @@ export class CollectionStackingViewFieldColumn extends React.Component { this._createAliasSelected = false; - if (de.data instanceof DragManager.DocumentDragData) { + if (de.complete.docDragData) { const key = StrCast(this.props.parent.props.Document.sectionFilter); const castedValue = this.getValue(this._heading); if (castedValue) { - de.data.droppedDocuments.forEach(d => d[key] = castedValue); + de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue); } else { - de.data.droppedDocuments.forEach(d => d[key] = undefined); + de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined); } this.props.parent.drop(e, de); e.stopPropagation(); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 9fa244546..5c7794cc0 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -27,7 +27,7 @@ import { Networking } from "../../Network"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc) => boolean; removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; PanelWidth: () => number; PanelHeight: () => number; VisibleHeight?: () => number; @@ -51,7 +51,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); if (ele) { - this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view @@ -132,32 +132,33 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { + const docDragData = de.complete.docDragData; (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.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this + if (docDragData && !docDragData.applyAsTemplate) { + if (de.altKey && docDragData.draggedDocuments.length) { this.childDocs.map(doc => - Doc.ApplyTemplateTo(de.data.draggedDocuments[0], doc, "layoutFromParent")); + Doc.ApplyTemplateTo(docDragData.draggedDocuments[0], doc, "layoutFromParent")); e.stopPropagation(); return true; } let added = false; - if (de.data.dropAction || de.data.userDropAction) { - added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); - } else if (de.data.moveDocument) { - const movedDocs = de.data.draggedDocuments; + if (docDragData.dropAction || docDragData.userDropAction) { + added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); + } else if (docDragData.moveDocument) { + const movedDocs = docDragData.draggedDocuments; added = movedDocs.reduce((added: boolean, d, i) => - de.data.droppedDocuments[i] !== d ? this.props.addDocument(de.data.droppedDocuments[i]) : - de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false); + docDragData.droppedDocuments[i] !== d ? this.props.addDocument(docDragData.droppedDocuments[i]) : + docDragData.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); + added = docDragData.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false); } e.stopPropagation(); return added; } - else if (de.data instanceof DragManager.AnnotationDragData) { + else if (de.complete.annoDragData) { e.stopPropagation(); - return this.props.addDocument(de.data.dropDocument); + return this.props.addDocument(de.complete.annoDragData.dropDocument); } return false; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ec1e7409f..0a22a897c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -123,7 +123,7 @@ class TreeView extends React.Component { @undoBatch delete = () => this.props.deleteDoc(this.props.document); @undoBatch openRight = () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight", this.props.libraryPath); @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete(); - @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => { + @undoBatch move = (doc: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => { return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); } @undoBatch @action remove = (document: Document, key: string) => { @@ -132,7 +132,7 @@ class TreeView extends React.Component { protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer && this._treedropDisposer(); - ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } })); + ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this))); } onPointerDown = (e: React.PointerEvent) => e.stopPropagation(); @@ -220,25 +220,25 @@ class TreeView extends React.Component { const rect = this._header!.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && DocListCast(this.dataDoc[this.fieldKey]).length); - if (de.data instanceof DragManager.LinkDragData) { - const sourceDoc = de.data.linkSourceDocument; + if (de.complete.linkDragData) { + const sourceDoc = de.complete.linkDragData.linkSourceDocument; const destDoc = this.props.document; DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }); e.stopPropagation(); } - if (de.data instanceof DragManager.DocumentDragData) { + if (de.complete.docDragData) { e.stopPropagation(); - if (de.data.draggedDocuments[0] === this.props.document) return true; + if (de.complete.docDragData.draggedDocuments[0] === this.props.document) return true; let addDoc = (doc: Doc) => this.props.addDocument(doc, undefined, before); if (inside) { addDoc = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) || addDoc(doc); } - const movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments); - return ((de.data.dropAction && (de.data.options !== this.props.treeViewId)) || de.data.userDropAction) ? - de.data.droppedDocuments.reduce((added, d) => addDoc(d) || added, false) - : de.data.moveDocument ? - movedDocs.reduce((added, d) => de.data.moveDocument(d, undefined, addDoc) || added, false) - : de.data.droppedDocuments.reduce((added, d) => addDoc(d), false); + const movedDocs = (de.complete.docDragData.treeViewId === this.props.treeViewId ? de.complete.docDragData.draggedDocuments : de.complete.docDragData.droppedDocuments); + return ((de.complete.docDragData.dropAction && (de.complete.docDragData.treeViewId !== this.props.treeViewId)) || de.complete.docDragData.userDropAction) ? + de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d) || added, false) + : de.complete.docDragData.moveDocument ? + movedDocs.reduce((added, d) => de.complete.docDragData?.moveDocument?.(d, undefined, addDoc) || added, false) + : de.complete.docDragData.droppedDocuments.reduce((added, d) => addDoc(d), false); } return false; } @@ -571,7 +571,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer && this.treedropDisposer(); if (this._mainEle = ele) { - this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } @@ -624,7 +624,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { render() { const dropAction = StrCast(this.props.Document.dropAction) as dropActionType; const addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, false); - const moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); + const moveDoc = (d: Doc, target: Doc | undefined, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); return !this.childDocs ? (null) : (
boolean; removeDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + moveDocument: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; active: () => boolean; whenActiveChanged: (isActive: boolean) => void; } @@ -150,7 +150,7 @@ export class CollectionView extends Touchable { // 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 { + moveDocument(doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean { if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { return true; } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 4161e5d6e..a870b6043 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -288,15 +288,15 @@ export class CollectionViewBaseChrome extends React.Component { this.dropDisposer && this.dropDisposer(); if (ele) { - this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } @undoBatch @action protected drop(e: Event, de: DragManager.DropEvent): boolean { - if (de.data instanceof DragManager.DocumentDragData && de.data.draggedDocuments.length) { - this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.data.draggedDocuments)); + if (de.complete.docDragData && de.complete.docDragData.draggedDocuments.length) { + this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(de.complete.docDragData?.draggedDocuments || [])); e.stopPropagation(); } return true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ddfb52ba9..89c1df598 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -139,15 +139,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const [xp, yp] = xf.transformPoint(de.x, de.y); const [xpo, ypo] = xfo.transformPoint(de.x, de.y); if (super.drop(e, de)) { - if (de.data instanceof DragManager.DocumentDragData) { - if (de.data.droppedDocuments.length) { - const firstDoc = de.data.droppedDocuments[0]; + if (de.complete.docDragData) { + if (de.complete.docDragData.droppedDocuments.length) { + const firstDoc = de.complete.docDragData.droppedDocuments[0]; const z = NumCast(firstDoc.z); - const x = (z ? xpo : xp) - de.data.offset[0]; - const y = (z ? ypo : yp) - de.data.offset[1]; + const x = (z ? xpo : xp) - de.complete.docDragData.offset[0]; + const y = (z ? ypo : yp) - de.complete.docDragData.offset[1]; const dropX = NumCast(firstDoc.x); const dropY = NumCast(firstDoc.y); - de.data.droppedDocuments.forEach(action((d: Doc) => { + de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => { const layoutDoc = Doc.Layout(d); d.x = x + NumCast(d.x) - dropX; d.y = y + NumCast(d.y) - dropY; @@ -162,19 +162,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.bringToFront(d); })); - de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); + de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]); } } - else if (de.data instanceof DragManager.AnnotationDragData) { - if (de.data.dropDocument) { - const dragDoc = de.data.dropDocument; - const x = xp - de.data.offset[0]; - const y = yp - de.data.offset[1]; + else if (de.complete.annoDragData) { + if (de.complete.annoDragData.dropDocument) { + const dragDoc = de.complete.annoDragData.dropDocument; + const x = xp - de.complete.annoDragData.offset[0]; + const y = yp - de.complete.annoDragData.offset[1]; const dropX = NumCast(dragDoc.x); const dropY = NumCast(dragDoc.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; dragDoc.y = y + NumCast(dragDoc.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 + de.complete.annoDragData.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); } } @@ -205,10 +205,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; - DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { - handlers: { dragComplete: action(emptyFunction) }, - hideSource: !de.dropAction - }); + DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction }); return true; } } @@ -896,7 +893,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document return !this.extensionDoc ? (null) :
diff --git a/src/client/views/linking/LinkMenu.scss b/src/client/views/linking/LinkMenu.scss index a4018bd2d..7dee22f66 100644 --- a/src/client/views/linking/LinkMenu.scss +++ b/src/client/views/linking/LinkMenu.scss @@ -48,90 +48,5 @@ } } -.linkMenu-item { - // border-top: 0.5px solid $main-accent; - position: relative; - display: flex; - font-size: 12px; - - - .link-name { - position: relative; - - p { - padding: 4px 6px; - line-height: 12px; - border-radius: 5px; - overflow-wrap: break-word; - } - } - - .linkMenu-item-content { - width: 100%; - } - - .link-metadata { - padding: 0 10px 0 16px; - margin-bottom: 4px; - color: $main-accent; - font-style: italic; - font-size: 10.5px; - } - - &:hover { - .linkMenu-item-buttons { - display: flex; - } - .linkMenu-item-content { - &.expand-two p { - width: calc(100% - 52px); - background-color: lightgray; - } - &.expand-three p { - width: calc(100% - 84px); - background-color: lightgray; - } - } - } -} - -.linkMenu-item-buttons { - display: none; - position: absolute; - top: 50%; - right: 0; - transform: translateY(-50%); - - .button { - width: 20px; - height: 20px; - margin: 0; - margin-right: 6px; - border-radius: 50%; - cursor: pointer; - pointer-events: auto; - background-color: $dark-color; - color: $light-color; - font-size: 65%; - transition: transform 0.2s; - text-align: center; - position: relative; - - .fa-icon { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - - &:last-child { - margin-right: 0; - } - &:hover { - background: $main-accent; - } - } -} - diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 15aacbbc9..da56435f2 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -4,11 +4,9 @@ import { observer } from "mobx-react"; import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; -import { emptyFunction } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager, SetupDrag } from "../../util/DragManager"; import { LinkManager } from "../../util/LinkManager"; -import { UndoManager } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import './LinkMenu.scss'; import { LinkMenuItem } from "./LinkMenuItem"; @@ -21,7 +19,6 @@ interface LinkMenuGroupProps { showEditor: (linkDoc: Doc) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; docView: DocumentView; - } @observer @@ -44,27 +41,14 @@ export class LinkMenuGroup extends React.Component { e.stopPropagation(); } - onLinkButtonMoved = async (e: PointerEvent) => { - UndoManager.RunInBatch(() => { - if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { - document.removeEventListener("pointermove", this.onLinkButtonMoved); - document.removeEventListener("pointerup", this.onLinkButtonUp); + if (this._drag.current && (e.movementX > 1 || e.movementY > 1)) { + document.removeEventListener("pointermove", this.onLinkButtonMoved); + document.removeEventListener("pointerup", this.onLinkButtonUp); - const draggedDocs = this.props.group.map(linkDoc => { - const opp = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc); - if (opp) return opp; - }) as Doc[]; - const dragData = new DragManager.DocumentDragData(draggedDocs); - - DragManager.StartLinkedDocumentDrag([this._drag.current], dragData, e.x, e.y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - }, "drag links"); + let targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; + DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets); + } e.stopPropagation(); } diff --git a/src/client/views/linking/LinkMenuItem.scss b/src/client/views/linking/LinkMenuItem.scss new file mode 100644 index 000000000..fd0954f65 --- /dev/null +++ b/src/client/views/linking/LinkMenuItem.scss @@ -0,0 +1,87 @@ +@import "../globalCssVariables"; + +.linkMenu-item { + // border-top: 0.5px solid $main-accent; + position: relative; + display: flex; + font-size: 12px; + + + .linkMenu-name { + position: relative; + + p { + padding: 4px 6px; + line-height: 12px; + border-radius: 5px; + overflow-wrap: break-word; + user-select: none; + } + } + + .linkMenu-item-content { + width: 100%; + } + + .link-metadata { + padding: 0 10px 0 16px; + margin-bottom: 4px; + color: $main-accent; + font-style: italic; + font-size: 10.5px; + } + + &:hover { + .linkMenu-item-buttons { + display: flex; + } + .linkMenu-item-content { + &.expand-two p { + width: calc(100% - 52px); + background-color: lightgray; + } + &.expand-three p { + width: calc(100% - 84px); + background-color: lightgray; + } + } + } +} + +.linkMenu-item-buttons { + display: none; + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); + + .button { + width: 20px; + height: 20px; + margin: 0; + margin-right: 6px; + border-radius: 50%; + cursor: pointer; + pointer-events: auto; + background-color: $dark-color; + color: $light-color; + font-size: 65%; + transition: transform 0.2s; + text-align: center; + position: relative; + + .fa-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &:last-child { + margin-right: 0; + } + &:hover { + background: $main-accent; + } + } +} \ No newline at end of file diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index edf5e9c26..b7d27ee30 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -5,11 +5,11 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import { Doc } from '../../../new_fields/Doc'; import { Cast, StrCast } from '../../../new_fields/Types'; -import { DragLinkAsDocument } from '../../util/DragManager'; +import { DragManager } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; import { LinkFollowBox } from './LinkFollowBox'; -import './LinkMenu.scss'; +import './LinkMenuItem.scss'; import React = require("react"); library.add(faEye, faEdit, faTimes, faArrowRight, faChevronDown, faChevronUp); @@ -26,6 +26,9 @@ interface LinkMenuItemProps { @observer export class LinkMenuItem extends React.Component { private _drag = React.createRef(); + private _downX = 0; + private _downY = 0; + private _eleClone: any; @observable private _showMore: boolean = false; @action toggleShowMore() { this._showMore = !this._showMore; } @@ -55,6 +58,9 @@ export class LinkMenuItem extends React.Component { } onLinkButtonDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + this._eleClone = this._drag.current!.cloneNode(true); e.stopPropagation(); document.removeEventListener("pointermove", this.onLinkButtonMoved); document.addEventListener("pointermove", this.onLinkButtonMoved); @@ -75,11 +81,12 @@ export class LinkMenuItem extends React.Component { } onLinkButtonMoved = async (e: PointerEvent) => { - if (this._drag.current !== null && (e.movementX > 1 || e.movementY > 1)) { + if (this._drag.current !== null && Math.abs((e.clientX - this._downX) * (e.clientX - this._downX) + (e.clientY - this._downY) * (e.clientY - this._downY)) > 5) { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); - DragLinkAsDocument(this._drag.current, e.x, e.y, this.props.linkDoc, this.props.sourceDoc); + this._eleClone.style.transform = `translate(${e.x}px, ${e.y}px)`; + DragManager.StartLinkTargetsDrag(this._eleClone, e.x, e.y, this.props.sourceDoc, [this.props.linkDoc]); } e.stopPropagation(); } @@ -109,20 +116,21 @@ export class LinkMenuItem extends React.Component { } render() { - const keys = LinkManager.Instance.getMetadataKeysInGroup(this.props.groupType);//groupMetadataKeys.get(this.props.groupType); const canExpand = keys ? keys.length > 0 : false; return (
-
-

{StrCast(this.props.destinationDoc.title)}

+
+

{StrCast(this.props.destinationDoc.title)}

{canExpand ?
this.toggleShowMore()}>
: <>}
-
+
+ +
{this._showMore ? this.renderMetadata() : <>} diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 34151a311..d1272c266 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -46,7 +46,7 @@ export class ButtonBox extends DocComponent(Butt this.dropDisposer(); } if (ele) { - this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); } } @@ -65,9 +65,10 @@ export class ButtonBox extends DocComponent(Butt @undoBatch @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.map((d, i) => - d.onDragStart ? de.data.draggedDocuments[i] : d)); + const docDragData = de.complete.docDragData; + if (docDragData && e.target) { + this.props.Document[(e.target as any).textContent] = new List(docDragData.droppedDocuments.map((d, i) => + d.onDragStart ? docDragData.draggedDocuments[i] : d)); e.stopPropagation(); } } diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 6d0ea33fa..5e3306a11 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -32,7 +32,7 @@ interface ContentFittingDocumentViewProps { onClick?: ScriptField; getTransform: () => Transform; addDocument: (document: Doc) => boolean; - moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean; + moveDocument: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean; removeDocument: (document: Doc) => boolean; active: (outsideReaction: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; @@ -60,11 +60,12 @@ export class ContentFittingDocumentView extends React.Component { - if (de.data instanceof DragManager.DocumentDragData) { + const docDragData = de.complete.docDragData; + if (docDragData) { this.props.childDocs && this.props.childDocs.map(otherdoc => { const target = Doc.GetProto(otherdoc); target.layout = ComputedField.MakeFunction("this.image_data[0]"); - target.layoutCustom = Doc.MakeDelegate(de.data.draggedDocuments[0]); + target.layoutCustom = Doc.MakeDelegate(docDragData.draggedDocuments[0]); }); e.stopPropagation(); } diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx index a22472e9e..3e2e74c67 100644 --- a/src/client/views/nodes/DocuLinkBox.tsx +++ b/src/client/views/nodes/DocuLinkBox.tsx @@ -5,7 +5,7 @@ import { makeInterface } from "../../../new_fields/Schema"; import { NumCast, StrCast, Cast } from "../../../new_fields/Types"; import { Utils } from '../../../Utils'; import { DocumentManager } from "../../util/DocumentManager"; -import { DragLinksAsDocuments } from "../../util/DragManager"; +import { DragManager } from "../../util/DragManager"; import { DocComponent } from "../DocComponent"; import "./DocuLinkBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; @@ -43,7 +43,7 @@ export class DocuLinkBox extends DocComponent(Doc const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); const 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], Cast(this.props.Document[this.props.fieldKey], Doc) as Doc, this.props.Document); // Containging collection is the document, not a collection... hack. + DragManager.StartLinkTargetsDrag(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 1b2d92027..b096d68fc 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -19,7 +19,6 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { ClientUtils } from '../../util/ClientUtils'; 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'; @@ -60,7 +59,7 @@ export interface DocumentViewProps { dragDivName?: string; addDocument?: (doc: Doc) => boolean; removeDocument?: (doc: Doc) => boolean; - moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + moveDocument?: (doc: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; renderDepth: number; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; @@ -105,7 +104,7 @@ export class DocumentView extends DocComponent(Docu @action componentDidMount() { - this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); + this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @@ -113,7 +112,7 @@ export class DocumentView extends DocComponent(Docu @action componentDidUpdate() { this._dropDisposer && this._dropDisposer(); - this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); + this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this))); } @action @@ -132,12 +131,7 @@ export class DocumentView extends DocComponent(Docu dragData.moveDocument = this.Document.onDragStart ? undefined : this.props.moveDocument; dragData.applyAsTemplate = applyAsTemplate; dragData.dragDivName = this.props.dragDivName; - DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { - handlers: { - dragComplete: action((emptyFunction)) - }, - hideSource: !dropAction && !this.Document.onDragStart - }); + DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { hideSource: !dropAction && !this.Document.onDragStart }); } } @@ -190,7 +184,7 @@ export class DocumentView extends DocComponent(Docu buttonClick = async (altKey: boolean, ctrlKey: boolean) => { const maximizedDocs = await DocListCastAsync(this.Document.maximizedDocs); const summarizedDocs = await DocListCastAsync(this.Document.summarizedDocs); - const linkDocs = LinkManager.Instance.getAllRelatedLinks(this.props.Document); + const linkDocs = DocListCast(this.props.Document.links); let expandedDocs: Doc[] = []; expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs; expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs; @@ -324,23 +318,24 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.AnnotationDragData) { + if (de.complete.annoDragData) { /// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner e.stopPropagation(); - (de.data as any).linkedToDoc = true; + de.complete.annoDragData.linkedToDoc = true; - DocUtils.MakeLink({ doc: de.data.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, `Link from ${StrCast(de.data.annotationDocument.title)}`); + DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, + `Link from ${StrCast(de.complete.annoDragData.annotationDocument.title)}`); } - if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) { - Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, "layoutCustom"); + if (de.complete.docDragData && de.complete.docDragData.applyAsTemplate) { + Doc.ApplyTemplateTo(de.complete.docDragData.draggedDocuments[0], this.props.Document, "layoutCustom"); e.stopPropagation(); } - if (de.data instanceof DragManager.LinkDragData) { + if (de.complete.linkDragData) { e.stopPropagation(); // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); - de.data.linkSourceDocument !== this.props.Document && - (de.data.linkDocument = DocUtils.MakeLink({ doc: de.data.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed + de.complete.linkDragData.linkSourceDocument !== this.props.Document && + (de.complete.linkDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.linkDragData.linkSourceDocument }, { doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, "in-text link being created")); // TODODO this is where in text links get passed } } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ce1c468ad..c56fde186 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -39,7 +39,7 @@ export interface FieldViewProps { addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; removeDocument?: (document: Doc) => boolean; - moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; + moveDocument?: (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; active: (outsideReaction?: boolean) => boolean; whenActiveChanged: (isActive: boolean) => void; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 7d404b28f..af09e57f4 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -246,22 +246,22 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & protected createDropTarget = (ele: HTMLDivElement) => { this._proseRef = ele; this.dropDisposer && this.dropDisposer(); - ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } })); + ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; + if (de.complete.docDragData) { + const draggedDoc = de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0]; // replace text contents whend dragging with Alt - if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") { + if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { if (draggedDoc.data instanceof RichTextField) { Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); 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") { + } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.metaKey) { draggedDoc.isTemplateDoc = true; let newLayout = Doc.Layout(draggedDoc); if (typeof (draggedDoc.layout) === "string") { @@ -272,8 +272,8 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & 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) { - const target = de.data.droppedDocuments[0]; + } else if (de.complete.docDragData.userDropAction || de.complete.docDragData.embedDoc) { + const target = de.complete.docDragData.droppedDocuments[0]; // const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); // if (link) { target.fitToBox = true; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 4b3da3dae..f6aa47f15 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -67,18 +67,18 @@ export class ImageBox extends DocAnnotatableComponent { this._dropDisposer && this._dropDisposer(); - ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } })); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData) { - if (de.mods === "AltKey" && de.data.draggedDocuments.length && de.data.draggedDocuments[0].data instanceof ImageField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.data.draggedDocuments[0].data.url); + if (de.complete.docDragData) { + if (de.altKey && de.complete.docDragData.draggedDocuments.length && de.complete.docDragData.draggedDocuments[0].data instanceof ImageField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(de.complete.docDragData.draggedDocuments[0].data.url); e.stopPropagation(); } - de.mods === "MetaKey" && de.data.droppedDocuments.forEach(action((drop: Doc) => { + de.metaKey && de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => { this.extensionDoc && Doc.AddDocToList(Doc.GetProto(this.extensionDoc), "Alternates", drop); e.stopPropagation(); })); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index b114cd7ed..c85350264 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -409,6 +409,7 @@ export class PDFViewer extends DocAnnotatableComponent !(dragData as any).linkedToDoc && - DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF") - - }, - hideSource: false + DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, { + dragComplete: e => !e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc && + DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument, ctx: e.annoDragData.targetContext }, `Annotation from ${this.Document.title}`, "link from PDF") }); } } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index fe21c29c3..515c248f7 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -12,13 +12,11 @@ import { Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { SearchUtil } from '../../util/SearchUtil'; -import { MainView } from '../MainView'; import { FilterBox } from './FilterBox'; import "./FilterBox.scss"; import "./SearchBox.scss"; import { SearchItem } from './SearchItem'; import { IconBar } from './IconBar'; -import { string } from 'prop-types'; library.add(faTimes); diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index cdb6dcf7c..8cd4b3caa 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -4,14 +4,13 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils, emptyPath } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, SetupDrag } from "../../util/DragManager"; -import { LinkManager } from "../../util/LinkManager"; import { SearchUtil } from "../../util/SearchUtil"; import { Transform } from "../../util/Transform"; import { SEARCH_THUMBNAIL_SIZE } from "../../views/globalCssVariables.scss"; @@ -211,7 +210,7 @@ export class SearchItem extends React.Component { } @computed - get linkCount() { return LinkManager.Instance.getAllRelatedLinks(this.props.doc).length; } + get linkCount() { return DocListCast(this.props.doc.links).length; } @action pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } @@ -267,10 +266,7 @@ export class SearchItem extends React.Component { onPointerDown = (e: React.PointerEvent) => { e.stopPropagation(); const doc = Doc.IsPrototype(this.props.doc) ? Doc.MakeDelegate(this.props.doc) : this.props.doc; - DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, { - handlers: { dragComplete: emptyFunction }, - hideSource: false, - }); + DragManager.StartDocumentDrag([e.currentTarget], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY); } render() { -- cgit v1.2.3-70-g09d2 From c21958664a8050fbe9d420c41330c1adcc402ae6 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Dec 2019 15:26:08 -0500 Subject: fixed script box draggign w/ parameters --- src/client/views/ScriptBox.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 65d96f364..956e83238 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -113,7 +113,15 @@ export class ScriptBox extends React.Component { return; } - params.length && DragManager.StartButtonDrag([], text, "a script", {}, params, (button: Doc) => { }, clientX, clientY); + var div = document.createElement("div"); + div.style.width = "90"; + div.style.height = "20"; + div.style.background = "gray"; + div.style.position = "absolute"; + div.style.display = "inline-block"; + div.style.transform = `translate(${clientX}px, ${clientY}px)`; + div.innerHTML = "button"; + params.length && DragManager.StartButtonDrag([div], text, doc.title + "-instance", {}, params, (button: Doc) => { }, clientX, clientY); doc[fieldKey] = new ScriptField(script); overlayDisposer(); -- cgit v1.2.3-70-g09d2 From 1050212f66f9a906e9f2847e6d6171f488ba2179 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 16:09:20 -0500 Subject: small changes --- src/server/ChildProcessUtilities/daemon/session.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 833e90581..d51240349 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -81,8 +81,7 @@ async function checkHeartbeat() { try { await request.get(heartbeat); if (restarting) { - addLogEntry(`Backup server successfully ${count ? "restarted" : "started"}`, green); - count++; + addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); } restarting = false; } catch (e) { @@ -98,7 +97,8 @@ async function checkHeartbeat() { identifiedLog(yellow("Cleaning up previous connections...")); await clear_ports(1050, 4321); - identifiedLog(yellow("Finished attempting to clear all ports. Any failures will be printed in red immediately above.")); + identifiedLog(yellow("Finished attempting to clear all ports.")); + identifiedLog(yellow("Any failures will be printed in red immediately above.")); await log_execution({ startMessage: identifier + " Sending crash notification email", -- cgit v1.2.3-70-g09d2 From 4d1fbc9b7e3dcccf5334d08739b25025e517ce13 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 12 Dec 2019 16:15:03 -0500 Subject: working on search result layout --- src/client/views/search/SearchBox.tsx | 9 ++++----- src/client/views/search/SearchItem.tsx | 23 +++++++++-------------- 2 files changed, 13 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 515c248f7..b80c3bb54 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -339,7 +339,7 @@ export class SearchBox extends React.Component { render() { return ( -
+
{ e.stopPropagation(); e.preventDefault(); }}>
@@ -349,10 +349,9 @@ export class SearchBox extends React.Component { style={{ width: this._searchbarOpen ? "500px" : "100px" }} />
- {(this._numTotalResults > 0 || !this._searchbarOpen) ? (null) : - (
-
-
)} +
+
+
{ if (!this._useIcons) { const returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); const returnYDimension = () => this._displayDim; - const scale = () => returnXDimension() / NumCast(Doc.Layout(this.props.doc).nativeWidth, returnXDimension()); const docview =
{ this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); })} - onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} - onPointerLeave={action(() => this._displayDim = 50)} > - this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} > +
; return docview; @@ -287,7 +282,7 @@ export class SearchItem extends React.Component {
{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}
{this.props.lines.filter((m, i) => i).map((l, i) =>
`${l}`
)}
-
+
{this.DocumentIcon()}
{this.props.doc.type ? this.props.doc.type : "Other"}
-- cgit v1.2.3-70-g09d2 From 15fd03eb0b3d408ba616e23419ec7f2d6ae17f50 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Dec 2019 17:38:35 -0500 Subject: from previous --- src/client/util/DocumentManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 6f65e6c68..1cb8b55d3 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -108,7 +108,7 @@ export class DocumentManager { return init && rest; }) ).reduce((pairs, dv) => { - const linksList = DocListCast(dv.props.Document.links); + const linksList = LinkManager.Instance.getAllRelatedLinks(dv.props.Document); pairs.push(...linksList.reduce((pairs, link) => { const linkToDoc = link && LinkManager.Instance.getOppositeAnchor(link, dv.props.Document); linkToDoc && DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { -- cgit v1.2.3-70-g09d2 From 551eccb37f9dad21886a85f0e91571aa588ea505 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 17:41:53 -0500 Subject: read write --- src/server/ChildProcessUtilities/daemon/session.ts | 51 +++++++++++++++------- 1 file changed, 35 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index d51240349..3ae14e894 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -5,12 +5,33 @@ import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; -import { ChildProcess, exec } from "child_process"; +import { ChildProcess, exec, execSync } from "child_process"; +import { createInterface } from "readline"; const killport = require("kill-port"); +process.on('SIGINT', endPrevious); const identifier = yellow("__session_manager__:"); -process.on('SIGINT', () => current_backup?.kill("SIGTERM")); +let manualRestartActive = false; +createInterface(process.stdin, process.stdout).on('line', async line => { + const prompt = line.trim().toLowerCase(); + switch (prompt) { + case "restart": + manualRestartActive = true; + identifiedLog(cyan("Initializing manual restart...")); + endPrevious(); + break; + case "exit": + identifiedLog(cyan("Initializing session end")); + await endPrevious(); + identifiedLog("Cleanup complete. Exiting session..."); + execSync("killall -9 node"); + break; + default: + identifiedLog(red("commands: { exit, restart }")); + return; + } +}); const logPath = resolve(__dirname, "./logs"); const crashPath = resolve(logPath, "./crashes"); @@ -37,6 +58,7 @@ if (!["win32", "darwin"].includes(process.platform)) { process.exit(1); } +const ports = [1050, 4321]; const onWindows = process.platform === "win32"; const LOCATION = "http://localhost"; const heartbeat = `${LOCATION}:1050/serverHeartbeat`; @@ -67,11 +89,14 @@ function timestamp() { return `@ ${new Date().toISOString()}`; } -async function clear_ports(...targets: number[]) { - return Promise.all(targets.map(port => { +async function endPrevious() { + identifiedLog(yellow("Cleaning up previous connections...")); + current_backup?.kill("SIGTERM"); + await Promise.all(ports.map(port => { const task = killport(port, 'tcp'); return task.catch((error: any) => identifiedLog(red(error))); })); + identifiedLog(yellow("Done. Any failures will be printed in red immediately above.")); } let current_backup: ChildProcess | undefined = undefined; @@ -80,26 +105,20 @@ async function checkHeartbeat() { let error: any; try { await request.get(heartbeat); - if (restarting) { + if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); + restarting = false; } - restarting = false; } catch (e) { error = e; } finally { if (error) { - if (!restarting) { + if (!restarting || manualRestartActive) { restarting = true; - if (count) { + if (count && !manualRestartActive) { console.log(); addLogEntry("Detected a server crash", red); - current_backup?.kill("SIGTERM"); - - identifiedLog(yellow("Cleaning up previous connections...")); - await clear_ports(1050, 4321); - identifiedLog(yellow("Finished attempting to clear all ports.")); - identifiedLog(yellow("Any failures will be printed in red immediately above.")); - + await endPrevious(); await log_execution({ startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { @@ -109,9 +128,9 @@ async function checkHeartbeat() { action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - identifiedLog(green("Initiating server restart...")); } + manualRestartActive = false; current_backup = exec(startServerCommand(), err => identifiedLog(err?.message || count ? "Previous server process exited." : "Spawned initial server.")); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } -- cgit v1.2.3-70-g09d2 From 5aaaf39260cf998008403fdb6f408f7c27469edc Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 17:44:59 -0500 Subject: kill all for windows --- src/server/ChildProcessUtilities/daemon/session.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 3ae14e894..4a62381f8 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -24,8 +24,8 @@ createInterface(process.stdin, process.stdout).on('line', async line => { case "exit": identifiedLog(cyan("Initializing session end")); await endPrevious(); - identifiedLog("Cleanup complete. Exiting session..."); - execSync("killall -9 node"); + identifiedLog("Cleanup complete. Exiting session...\n"); + execSync(killAllCommand()); break; default: identifiedLog(red("commands: { exit, restart }")); @@ -75,6 +75,13 @@ function startServerCommand() { return `osascript -e 'tell app "Terminal"\ndo script "cd ${pathFromRoot()} && npm run start-release"\nend tell'`; } +function killAllCommand() { + if (onWindows) { + return "taskkill /f /im node.exe"; + } + return "killall -9 node"; +} + identifiedLog("Initializing session..."); writeLocalPidLog("session_manager", pid); -- cgit v1.2.3-70-g09d2 From 634475c34cdc49672edebb722e469ed627d495c2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Dec 2019 20:08:05 -0500 Subject: slight changes to session heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 4a62381f8..f3d9b42c3 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -111,12 +111,15 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { + identifiedLog(green("request Heartbeat...")); await request.get(heartbeat); + identifiedLog(green("got Heartbeat...")); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; } } catch (e) { + identifiedLog(red("failed Heartbeat..." + e)); error = e; } finally { if (error) { @@ -142,6 +145,8 @@ async function checkHeartbeat() { writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } } + identifiedLog(green("restarting heartbeater")); + setTimeout(checkHeartbeat, 1000 * frequency); } } @@ -152,7 +157,6 @@ async function startListening() { process.exit(0); } await checkHeartbeat(); - setInterval(checkHeartbeat, 1000 * frequency); } function emailText(error: any) { -- cgit v1.2.3-70-g09d2 From 27a124671155f5178691707a4fff73aa071cd741 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 20:44:30 -0500 Subject: small session changes --- src/server/ChildProcessUtilities/daemon/session.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index f3d9b42c3..35c23b525 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -58,12 +58,12 @@ if (!["win32", "darwin"].includes(process.platform)) { process.exit(1); } +const latency = 10; const ports = [1050, 4321]; const onWindows = process.platform === "win32"; const LOCATION = "http://localhost"; const heartbeat = `${LOCATION}:1050/serverHeartbeat`; const recipient = "samuel_wilkins@brown.edu"; -const frequency = 10; const { pid } = process; let restarting = false; let count = 0; @@ -111,15 +111,18 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { - identifiedLog(green("request Heartbeat...")); + count && identifiedLog(green("Requesting heartbeat")); await request.get(heartbeat); - identifiedLog(green("got Heartbeat...")); + count && identifiedLog(green("Received heartbeat")); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; } } catch (e) { - identifiedLog(red("failed Heartbeat..." + e)); + if (count) { + identifiedLog(red("Heartbeat failed...")); + identifiedLog(red(e.message)); + } error = e; } finally { if (error) { @@ -145,13 +148,12 @@ async function checkHeartbeat() { writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } } - identifiedLog(green("restarting heartbeater")); - setTimeout(checkHeartbeat, 1000 * frequency); + setTimeout(checkHeartbeat, 1000 * latency); } } async function startListening() { - identifiedLog(yellow(`After initialization, will poll server heartbeat every ${frequency} seconds...\n`)); + identifiedLog(yellow(`After initialization, will poll server heartbeat repeatedly...\n`)); if (!LOCATION) { identifiedLog(red("No location specified for session manager. Please include as a command line environment variable or in a .env file.")); process.exit(0); -- cgit v1.2.3-70-g09d2 From eead07720f1f196e9ee81cd44b26759dc44e04f9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Dec 2019 21:00:36 -0500 Subject: fixed warnings. Limited the size of pasted text boxes --- src/client/documents/Documents.ts | 1 + src/client/util/DragManager.ts | 14 ++++++------- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/PreviewCursor.tsx | 32 +++++++++++------------------ src/client/views/ScriptBox.tsx | 2 +- src/client/views/linking/LinkMenuGroup.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 11 +++++++--- 7 files changed, 30 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5d5bdfcbd..7df08c7e6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -112,6 +112,7 @@ export interface DocumentOptions { dropConverter?: ScriptField; // script to run when documents are dropped on this Document. strokeWidth?: number; color?: string; + limitHeight?:number; // maximum height for newly created (eg, from pasting) text documents // [key: string]: Opt; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 95529311a..326262895 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -101,10 +101,10 @@ export namespace DragManager { export class DragCompleteEvent { constructor(aborted: boolean, dragData: { [id: string]: any }) { this.aborted = aborted; - this.docDragData = dragData instanceof DocumentDragData ? dragData as DocumentDragData : undefined; - this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData as PdfAnnoDragData : undefined; - this.linkDragData = dragData instanceof LinkDragData ? dragData as LinkDragData : undefined; - this.columnDragData = dragData instanceof ColumnDragData ? dragData as ColumnDragData : undefined; + this.docDragData = dragData instanceof DocumentDragData ? dragData : undefined; + this.annoDragData = dragData instanceof PdfAnnoDragData ? dragData : undefined; + this.linkDragData = dragData instanceof LinkDragData ? dragData : undefined; + this.columnDragData = dragData instanceof ColumnDragData ? dragData : undefined; } aborted: boolean; docDragData?: DocumentDragData; @@ -140,14 +140,12 @@ export namespace DragManager { linkSourceDocument: Doc; dontClearTextBox?: boolean; linkDocument?: Doc; - [id: string]: any; } export class ColumnDragData { constructor(colKey: SchemaHeaderField) { this.colKey = colKey; } colKey: SchemaHeaderField; - [id: string]: any; } // used by PDFs to conditionally (if the drop completes) create a text annotation when dragging from the PDF toolbar when a text region has been selected. // this is pretty clunky and should be rethought out using linkDrag or DocumentDrag @@ -216,7 +214,7 @@ export namespace DragManager { // drag links and drop link targets (aliasing them if needed) export async function StartLinkTargetsDrag(dragEle: HTMLElement, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) { - let draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; + const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; if (draggedDocs.length) { const moddrag: Doc[] = []; @@ -225,7 +223,7 @@ export namespace DragManager { if (doc) moddrag.push(doc); } - let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); + const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => { const document = SelectionManager.SelectedDocuments()[0]; document && document.props.removeDocument && document.props.removeDocument(doc); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index f014c1329..58728ab7f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -114,7 +114,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], const proto = Doc.GetProto(linkDoc); proto.sourceContext = this.props.views[0].props.ContainingCollectionDoc; - const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast((linkDoc.anchor2 as Doc).title) : "-untitled-"; + const anchor2Title = linkDoc.anchor2 instanceof Doc ? StrCast(linkDoc.anchor2.title) : "-untitled-"; const text = FormattedTextBox.ToolTipTextMenu.makeLink(linkDoc, anchor2Title, e.ctrlKey ? "onRight" : "inTab"); if (linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODO open to more descriptive descriptions of following in text link diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index fd0287b6c..8adcf1220 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import "./PreviewCursor.scss"; import { Docs } from '../documents/Documents'; // import { Transform } from 'prosemirror-transform'; -import { Doc } from '../../new_fields/Doc'; +import { Doc, HeightSym } from '../../new_fields/Doc'; import { Transform } from "../util/Transform"; import { TraceMobx } from '../../new_fields/util'; @@ -27,61 +27,53 @@ export class PreviewCursor extends React.Component<{}> { if (PreviewCursor.Visible) { if (e.clipboardData) { const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); - runInAction(() => { PreviewCursor.Visible = false; }); - + runInAction(() => PreviewCursor.Visible = false); if (e.clipboardData.getData("text/plain") !== "") { // tests for youtube and makes video document if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) { const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/"); - PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { + return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] })); - return; } // tests for URL and makes web document const re: any = /^https?:\/\//g; if (re.test(e.clipboardData.getData("text/plain"))) { const url = e.clipboardData.getData("text/plain"); - PreviewCursor._addDocument(Docs.Create.WebDocument(url, { - title: url, width: 300, height: 300, + return PreviewCursor._addDocument(Docs.Create.WebDocument(url, { + title: url, width: 500, height: 300, // nativeWidth: 300, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] })); - return; } // creates text document - const newBox = Docs.Create.TextDocument({ - width: 200, height: 100, + return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument({ + width: 500, + autoHeight: true, x: newPoint[0], y: newPoint[1], + limitHeight: 400, title: "-pasted text-" - }); - - newBox.proto!.autoHeight = true; - PreviewCursor._addLiveTextDoc(newBox); - return; + })); } //pasting in images if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes(" { return; } - var div = document.createElement("div"); + const div = document.createElement("div"); div.style.width = "90"; div.style.height = "20"; div.style.background = "gray"; diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index da56435f2..abd17ec4d 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -46,7 +46,7 @@ export class LinkMenuGroup extends React.Component { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); - let targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; + const targets = this.props.group.map(l => LinkManager.Instance.getOppositeAnchor(l, this.props.sourceDoc)).filter(d => d) as Doc[]; DragManager.StartLinkTargetsDrag(this._drag.current!, e.x, e.y, this.props.sourceDoc, targets); } e.stopPropagation(); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index af09e57f4..a298fd6af 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -630,7 +630,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & { fireImmediately: true } ); - setTimeout(() => this.tryUpdateHeight(), 0); + setTimeout(() => this.tryUpdateHeight(NumCast(this.layoutDoc.limitHeight, 0))); } pushToGoogleDoc = async () => { @@ -1087,10 +1087,15 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } @action - tryUpdateHeight() { - const scrollHeight = this._ref.current?.scrollHeight; + tryUpdateHeight(limitHeight?: number) { + let scrollHeight = this._ref.current?.scrollHeight; if (!this.layoutDoc.animateToPos && this.layoutDoc.autoHeight && scrollHeight && 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 + if (limitHeight && scrollHeight > limitHeight) { + scrollHeight = limitHeight; + this.layoutDoc.limitHeight = undefined; + this.layoutDoc.autoHeight = false; + } const nh = this.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); const dh = NumCast(this.layoutDoc.height, 0); const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); -- cgit v1.2.3-70-g09d2 From e488f3b2ab8e664852697619a55a2f279488f456 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Dec 2019 21:08:28 -0500 Subject: from last --- src/client/views/PreviewCursor.tsx | 90 ++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 8adcf1220..208bc2b70 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -1,11 +1,10 @@ -import { action, observable, runInAction, trace } from 'mobx'; +import { action, observable } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import "./PreviewCursor.scss"; import { Docs } from '../documents/Documents'; -// import { Transform } from 'prosemirror-transform'; -import { Doc, HeightSym } from '../../new_fields/Doc'; +import { Doc } from '../../new_fields/Doc'; import { Transform } from "../util/Transform"; import { TraceMobx } from '../../new_fields/util'; @@ -24,56 +23,53 @@ export class PreviewCursor extends React.Component<{}> { } paste = (e: ClipboardEvent) => { - if (PreviewCursor.Visible) { - if (e.clipboardData) { - const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); - runInAction(() => PreviewCursor.Visible = false); + if (PreviewCursor.Visible && e.clipboardData) { + const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); + PreviewCursor.Visible = false; - if (e.clipboardData.getData("text/plain") !== "") { - - // tests for youtube and makes video document - if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) { - const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/"); - return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { - title: url, width: 400, height: 315, - nativeWidth: 600, nativeHeight: 472.5, - x: newPoint[0], y: newPoint[1] - })); - } - - // tests for URL and makes web document - const re: any = /^https?:\/\//g; - if (re.test(e.clipboardData.getData("text/plain"))) { - const url = e.clipboardData.getData("text/plain"); - return PreviewCursor._addDocument(Docs.Create.WebDocument(url, { - title: url, width: 500, height: 300, - // nativeWidth: 300, nativeHeight: 472.5, - x: newPoint[0], y: newPoint[1] - })); - } - - // creates text document - return PreviewCursor._addLiveTextDoc(Docs.Create.TextDocument({ - width: 500, - autoHeight: true, - x: newPoint[0], - y: newPoint[1], - limitHeight: 400, - title: "-pasted text-" + if (e.clipboardData.getData("text/plain") !== "") { + // tests for youtube and makes video document + if (e.clipboardData.getData("text/plain").indexOf("www.youtube.com/watch") !== -1) { + const url = e.clipboardData.getData("text/plain").replace("youtube.com/watch?v=", "youtube.com/embed/"); + return PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { + title: url, width: 400, height: 315, + nativeWidth: 600, nativeHeight: 472.5, + x: newPoint[0], y: newPoint[1] })); } - //pasting in images - if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes(" Date: Thu, 12 Dec 2019 21:15:03 -0500 Subject: heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 35c23b525..5155305ce 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -111,9 +111,9 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { - count && identifiedLog(green("Requesting heartbeat")); + count && !restarting && process.stdout.write(green(`${identifier} <`)); await request.get(heartbeat); - count && identifiedLog(green("Received heartbeat")); + count && !restarting && console.log(green("3")); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; -- cgit v1.2.3-70-g09d2 From 22f2f7dd970f70bd8151762e31613029fdbb2f29 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 21:18:34 -0500 Subject: heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 5155305ce..e2c72c1d5 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -120,6 +120,7 @@ async function checkHeartbeat() { } } catch (e) { if (count) { + console.log(); identifiedLog(red("Heartbeat failed...")); identifiedLog(red(e.message)); } -- cgit v1.2.3-70-g09d2 From c79923651eab568ec1be6eb542f937041c6e897c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 22:03:39 -0500 Subject: heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index e2c72c1d5..8d2845cf3 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -111,19 +111,15 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { - count && !restarting && process.stdout.write(green(`${identifier} <`)); + count && !restarting && process.stdout.write(`${identifier} 👂 `); await request.get(heartbeat); - count && !restarting && console.log(green("3")); + count && !restarting && console.log('⇠ 💚'); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; } } catch (e) { - if (count) { - console.log(); - identifiedLog(red("Heartbeat failed...")); - identifiedLog(red(e.message)); - } + count && !restarting && console.log("⇠ 💔"); error = e; } finally { if (error) { @@ -132,6 +128,7 @@ async function checkHeartbeat() { if (count && !manualRestartActive) { console.log(); addLogEntry("Detected a server crash", red); + identifiedLog(red(error.message)); await endPrevious(); await log_execution({ startMessage: identifier + " Sending crash notification email", -- cgit v1.2.3-70-g09d2 From a3a654ae509c03508916235664217731f5a91db2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com> Date: Fri, 13 Dec 2019 00:31:02 -0500 Subject: await end previous --- src/server/ChildProcessUtilities/daemon/session.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 8d2845cf3..fb2b3551e 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -19,7 +19,7 @@ createInterface(process.stdin, process.stdout).on('line', async line => { case "restart": manualRestartActive = true; identifiedLog(cyan("Initializing manual restart...")); - endPrevious(); + await endPrevious(); break; case "exit": identifiedLog(cyan("Initializing session end")); -- cgit v1.2.3-70-g09d2 From bce5f3aac4e31745e679c9eec92f836f3b136be9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 13 Dec 2019 00:35:54 -0500 Subject: fixed doc button bar to stay on screen --- src/client/views/MetadataEntryMenu.tsx | 7 ++--- src/client/views/TemplateMenu.scss | 8 +++--- src/client/views/TemplateMenu.tsx | 12 +++++---- .../views/collections/CollectionDockingView.scss | 2 +- .../views/collections/ParentDocumentSelector.scss | 12 +++++++-- .../views/collections/ParentDocumentSelector.tsx | 31 +++++++++++----------- 6 files changed, 41 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 8859c14cb..243cdb8f6 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -6,7 +6,7 @@ import { KeyValueBox } from './nodes/KeyValueBox'; import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc'; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; -import { emptyFunction } from '../../Utils'; +import { emptyFunction, emptyPath } from '../../Utils'; export type DocLike = Doc | Doc[] | Promise | Promise; export interface MetadataEntryProps { @@ -194,6 +194,7 @@ export class MetadataEntryMenu extends React.Component{ ); } + _ref = React.createRef(); render() { return (
@@ -201,14 +202,14 @@ export class MetadataEntryMenu extends React.Component{ Key: Value: - + this._ref.current!.focus()} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} /> {this.considerChildOptions}
diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss index 186d3ab0d..69bebe0e9 100644 --- a/src/client/views/TemplateMenu.scss +++ b/src/client/views/TemplateMenu.scss @@ -30,15 +30,15 @@ } .template-list { - position: absolute; - top: 25px; - left: 0px; - width: max-content; font-family: $sans-serif; font-size: 12px; background-color: $light-color-secondary; padding: 2px 12px; list-style: none; + position: relative; + display: inline-block; + height: 100%; + width: 100%; .templateToggle, .chromeToggle { text-align: left; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index f7cfc4022..598bcd56d 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -159,13 +159,15 @@ export class TemplateMenu extends React.Component { templateMenu.push(); templateMenu.push(); return ( -
-
this.toggleTemplateActivity()}>+
-
    + {templateMenu} {} -
-
+ }> +
+
this.toggleTemplateActivity()}>+
+
+ ); } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index bcdc9c97e..f518ef8fb 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -25,7 +25,7 @@ position: absolute; top: 0; left: 0; - overflow: hidden; + // overflow: hidden; // bcz: menus don't show up when this is on (e.g., the parentSelectorMenu) .collectionDockingView-dragAsDocument { touch-action: none; diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index aa25a900c..5d2245292 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -1,11 +1,19 @@ -.PDS-flyout { +.parentDocumentSelector-linkFlyout { + div { + overflow: visible !important; + } + .metadataEntry-outerDiv { + overflow: hidden !important; + pointer-events: all; + } +} +.parentDocumentSelector-flyout { position: absolute; z-index: 9999; background-color: #eeeeee; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); min-width: 150px; color: black; - top: 12px; padding: 10px; border-radius: 3px; diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 75ee8de32..b2f5c92b5 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -93,22 +93,21 @@ export class ParentDocSelector extends React.Component { } render() { - let flyout; - if (this.hover) { - flyout = ( -
- -
- ); - } - return ( - -

^

- {flyout} -
+ let flyout = ( +
+ +
); + return
+ + +

^

+
+
+
; } } @@ -131,7 +130,7 @@ export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any if (this.hover) { const view = DocumentManager.Instance.getDocumentView(this.props.Document); flyout = !view ? (null) : ( -
+
); -- cgit v1.2.3-70-g09d2 From 99564dfde417fe3fbdc8a13b43f73d34438f363c Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Dec 2019 11:10:11 -0500 Subject: fixed flyouts a bit more --- .../views/collections/ParentDocumentSelector.scss | 12 ++++++-- .../views/collections/ParentDocumentSelector.tsx | 35 +++++----------------- 2 files changed, 18 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index 5d2245292..0ea3e015d 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -8,7 +8,7 @@ } } .parentDocumentSelector-flyout { - position: absolute; + position: relative; z-index: 9999; background-color: #eeeeee; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); @@ -17,6 +17,10 @@ padding: 10px; border-radius: 3px; + display: inline-block; + height: 100%; + width: 100%; + border-radius: 3px; hr { height: 1px; @@ -29,7 +33,11 @@ } } .parentDocumentSelector-button { - pointer-events: all; + pointer-events: all; + position: relative; + display: inline-block; + padding-left: 5px; + padding-right: 5px; } .parentDocumentSelector-metadata { pointer-events: auto; diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index b2f5c92b5..5208f65c4 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -80,30 +80,16 @@ export class SelectorContextMenu extends React.Component { @observer export class ParentDocSelector extends React.Component { - @observable hover = false; - - @action - onMouseLeave = () => { - this.hover = false; - } - - @action - onMouseEnter = () => { - this.hover = true; - } - render() { let flyout = ( -
+
); - return
+ return
e.stopPropagation()} className="parentDocumentSelector-linkFlyout"> - +

^

@@ -116,13 +102,9 @@ export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any @observable hover = false; @action - onMouseLeave = () => { - this.hover = false; - } - - @action - onMouseEnter = () => { - this.hover = true; + onPointerDown = (e: React.PointerEvent) => { + this.hover = !this.hover; + e.stopPropagation(); } render() { @@ -130,15 +112,14 @@ export class ButtonSelector extends React.Component<{ Document: Doc, Stack: any if (this.hover) { const view = DocumentManager.Instance.getDocumentView(this.props.Document); flyout = !view ? (null) : ( -
+
); } return ( + onPointerDown={this.onPointerDown}> {this.hover ? (null) : } {flyout} -- cgit v1.2.3-70-g09d2 From 9a90830162cf176b58729385588accf3b589454d Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 13 Dec 2019 12:33:32 -0500 Subject: from last --- src/client/views/collections/ParentDocumentSelector.scss | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index 0ea3e015d..20e7cb994 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -12,7 +12,6 @@ z-index: 9999; background-color: #eeeeee; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); - min-width: 150px; color: black; padding: 10px; -- cgit v1.2.3-70-g09d2